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: {
Completedgildor
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
// Completedgildor
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