dephinera
02/20/2023, 8:37 AMsuspend fun loadData()
with withContext(<http://Dispatchers.IO|Dispatchers.IO>)
, or should the loadData()
function itself use withContext
, as the function is the one who knows there’s an actual IO. loadData()
might require some computational work instead of IO, in which case Dispatchers.Default
would be preferred (to make thinks more clear, I’ll leave examples in a thread). Is my understanding correct?
The only rule I can remember from the coroutines codelab from JB is that if the function takes a callback, you should call that callback outside of any withContext
blocks, as the consumer declares where the result should be handled.dephinera
02/20/2023, 8:37 AMclass MyVM(private val repository: Repository) {
fun init() {
viewModelScope.launch(Dispatchers.Main.Immediate) {
val dataToBeDisplayed = repository.loadData()
handleResult(dataToBeDisplayed)
}
}
}
class Repository {
suspend fun loadData(): Result {
return withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
doIO()
}
}
}
• Example 2
class MyVM(private val repository: Repository) {
fun init() {
viewModelScope.launch(Dispatchers.Main.Immediate) {
val dataToBeDisplayed = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
repository.loadData()
}
handleResult(dataToBeDisplayed)
}
}
}
class Repository {
suspend fun loadData(): Result {
return doIO()
}
}
Sam
02/20/2023, 8:40 AMsuspend
function should not call blocking functions, and should be safe to call on any dispatcher. The caller shouldn’t need to know the implementation details of the function in order to call it safely. So I agree, your first example is the correct choice 👍dephinera
02/20/2023, 8:53 AMSam
02/20/2023, 8:55 AMstojan
02/20/2023, 10:20 AMDispatchers.Main.Immediate
from the launch
as viewModelScope
runs on Dispatchers.Main.Immediate
by default
and yes, first option is the recommended one... functions are responsible for running on the correct scope:
https://developer.android.com/kotlin/coroutines/coroutines-best-practices#main-safegildor
02/21/2023, 2:20 AMSuspend function can call blocking function if wrap it correctly (with appropriate dispatcher and handle cancellation correctly when it’s possilbe), the convention is that suspend function shouldn’t be blocking by itselffunction should not call blocking functionssuspend
Sam
02/21/2023, 7:15 AMRyan Smith
02/22/2023, 1:04 AMSuspend function can call blocking function if wrap it correctly (with appropriate dispatcher and handle cancellation correctly when it’s possilbe)As an example, then, if I wanted to do something like file I/O what would be the correct way to manage it? I have an example here, if it helps:
suspend fun <T> serializeIt(thing: T) {
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
val file = File("out.json")
file.outputStream().use {
// Json from kotlinx.serialization
Json.encodeToStream(thing, it)
}
}
}
gildor
02/22/2023, 1:51 AMwithContext(…) {
use {
coroutineContext.job.invokeOnCompletion { it.close() }
}
}
Ryan Smith
02/22/2023, 1:57 AMinvokeOnCompletion
will shut down everything neatly?gildor
02/22/2023, 2:00 AMRyan Smith
02/22/2023, 2:05 AMuse
not enough to clean up the stream in the event of cancellation? Does cancellation not result in a CancellationException
being thrown?gildor
02/22/2023, 2:06 AMgildor
02/22/2023, 2:07 AMDoes cancellation not result in aIt would, but your code doesn’t call any suspend function, so cancellation will be thrown when withContext block is finihsed, it will be propagated on topbeing thrownCancellationException
gildor
02/22/2023, 2:15 AMval file = createTempFile()
try {
file.outputStream().use {
it.write("{".toByteArray())
throw CancellationException()
it.write("}".toByteArray())
}
} catch (e: CancellationException) {
println("Resulting file content: ${file.readText()}")
}
println("Completed")
gildor
02/22/2023, 2:15 AMResulting file content: {
Completed
gildor
02/22/2023, 2:18 AMval file = createTempFile()
try {
file.outputStream().use {
it.write("{".toByteArray())
throw CancellationException()
it.write("}".toByteArray())
}
} catch (e: Exception) {
try {
file.delete()
} catch (e: Exception) {
println("Not able to clean up")
}
println("Resulting file exists: ${file.exists()}")
}
println("Completed")
// Out:
// Resulting file exists: false
// Completed
gildor
02/22/2023, 2:20 AMRyan Smith
02/22/2023, 2:27 AMuse
and the block I pass to it are blocking, how does cancellation affect them? Is the enclosing scope not capable of cleaning up until my use
block returns normally (or exceptionally)?
From our discussion it sounds like what happens is the enclosing scope and all of it's children are canceled, leaving use
unaware that something exceptional has occurred. But what then? Does it run to completion, resulting in a cancellation exception when it returns? If so it seems that while the result of use
would be lost, the file output stream wouldn't leak since use
would return.gildor
02/22/2023, 2:34 AMgildor
02/22/2023, 2:34 AMRyan Smith
02/22/2023, 2:36 AMgildor
02/22/2023, 2:36 AMRyan Smith
02/22/2023, 2:39 AMgildor
02/22/2023, 2:41 AMRyan Smith
02/22/2023, 2:41 AMgildor
02/22/2023, 2:44 AMRyan Smith
02/22/2023, 2:49 AM