ansman
02/21/2019, 4:27 PMfun processImage() {
val file = createFile()
try {
// Do stuff with the file
} finally {
file.delete
}
}
suspend fun createFile(): File = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
return File.createTemporaryFile("foo", "temp")
}
There is a case where createFile
has finished but processImage
has not yet been resumed. If during this time the job is cancelled we’ll never get a chance to clean up the file. Is there a way around this?uli
02/21/2019, 4:52 PMfun processImage() {
val file : File? = null
try {
file = createFile()
// Do stuff with the file
} finally {
file?.delete
}
}
ansman
02/21/2019, 4:52 PMcreateFile
never actually “returned” a fileuli
02/21/2019, 4:53 PMFile
is only returned from the withContext lambda parameter, but not yet from createFileansman
02/21/2019, 4:55 PMuli
02/21/2019, 5:08 PMsuspend fun createFile(): File {
var file : File? = null
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
file = File.createTempFile(("foo", "temp")
}
yield()
return file
}
it would be obviouse that you have to fix it like this:
suspend fun createFile(): File {
var file: File? = null
try {
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
file = File.createTempFile(("foo", "temp")
}
yield()
} catch (Exception e) {
file.delete()
throw e
}
return file
}
trathschlag
02/21/2019, 5:36 PMsuspend fun processImage() {
useTemporaryFile { file ->
// do stuff with the file
}
}
suspend fun <R> useTemporaryFile(body: suspend (File) -> R) : R = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
val file = createTempFile("foo", "temp")
try {
withContext(Dispatchers.Default) {
body(file)
}
} finally {
file.delete()
}
}
this should always delete the fileuli
02/21/2019, 6:10 PMsuspend inline fun <R> useTemporaryFile(block: suspend (File) -> R): R {
var file: File? = null
try {
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
file = createTempFile("foo", "temp")
}
return file?.let { block(it) } ?: throw IllegalStateException()
} finally {
file?.delete()
}
}
If only `withContext`was already annotated with an appropriate contract, the compiler could probably smart cast file
and we would not need the ugly `let`/`throw`ansman
02/21/2019, 6:20 PMuli
02/21/2019, 6:23 PM// Don't use! This code is broken
suspend inline fun <R> useTemporaryFile(block: suspend (File) -> R): R {
var file: File? = null
try {
file = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
createTempFile("foo", "temp")
}
return block(file)
} finally {
file?.delete()
}
}
trathschlag
02/21/2019, 8:31 PMcoroutineContext
extension property:
suspend fun <R> useTemporaryFile(body: suspend (File) -> R): R {
val outerContext = coroutineContext
return withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
val file = createTempFile("foo", "temp")
try {
withContext(outerContext) {
body(file)
}
} finally {
file.delete()
}
}
}
but I'm still not sure if there are any pitfalls regarding this.
and for the general problem: what about
suspend fun processImage() {
val outerContext = coroutineContext
withContext(NonCancellable) {
val file = createFile()
try {
withContext(outerContext) {
// do something with file
}
} finally {
file.delete()
}
}
}
still not the most intuitiveuli
02/22/2019, 9:20 AMwithContext
should be designed to prevent canceling while returning to the calling context.
The current design allows for cancelation while there is no owner for the result object, allowing for it to be lost. This is 1) implicit and 2) surprising which are two things that Kotlin typically tries to avoid with it's designlouiscad
02/22/2019, 12:46 PMelizarov
02/22/2019, 2:00 PMwithContext
is designed on purpose. In a UI app, when we return back to the main thread we want to be sure that the UI is not destroyed (job is not cancelled) before we attempt to manipulate it.withContext
control this behavior, but have dropped it, since it is too advanced and hard to use anyway.withContext
. The is very similar to the following problem:
fun createFile(): File =
File.createTemporaryFile("foo", "temp")
.also { doSomethingElse() } /// ooops... what if it fails?
}
ansman
02/22/2019, 2:05 PMelizarov
02/22/2019, 2:06 PMwithContext
(maybe) with this kind of behavior.ansman
02/22/2019, 2:10 PMwithContext
though but rather all functions that actually suspendelizarov
02/22/2019, 2:10 PMansman
02/22/2019, 2:12 PMsuspendCoroutine
for exampleelizarov
02/22/2019, 2:12 PMval r = acquireResource()
try { ... } finally { r.close }
is inherently tricky. You have to make there is nothing between acquireResource
and try
and the implementation of acquireResource
need to have extreme care for exceptional cases.ansman
02/22/2019, 2:14 PMelizarov
02/22/2019, 2:15 PMval r = acquireResource()
launch {
try { ... } finally { r.close() }
}
It will not work, because launch may not even start.launch
and for withContext
ansman
02/22/2019, 2:16 PMelizarov
02/22/2019, 2:17 PMCoroutineStart
paramater for withContext
, too, but dropped it.