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 createFileuli
02/21/2019, 4:54 PMansman
02/21/2019, 4:55 PMuli
02/21/2019, 5:08 PMuli
02/21/2019, 5:11 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 PMuli
02/21/2019, 6:18 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 PMuli
02/21/2019, 6:25 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 intuitiveelizarov
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.elizarov
02/22/2019, 2:01 PMwithContext
control this behavior, but have dropped it, since it is too advanced and hard to use anyway.elizarov
02/22/2019, 2:02 PMwithContext
. 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.elizarov
02/22/2019, 2:06 PMansman
02/22/2019, 2:10 PMwithContext
though but rather all functions that actually suspendelizarov
02/22/2019, 2:10 PMelizarov
02/22/2019, 2:11 PMansman
02/22/2019, 2:12 PMsuspendCoroutine
for exampleansman
02/22/2019, 2:12 PMelizarov
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 PMansman
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.elizarov
02/22/2019, 2:15 PMlaunch
and for withContext
ansman
02/22/2019, 2:16 PMelizarov
02/22/2019, 2:17 PMCoroutineStart
paramater for withContext
, too, but dropped it.elizarov
02/22/2019, 2:43 PM