The documentation of withContext
states
Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result.
However, the actual behavior is that it awaits on all the child coroutines as well, and doesn't necessarily return the result of the block but instead propagates any exception in the child coroutine.
suspend fun main() {
try {
val result = withContext(coroutineContext) {
launch {
delay(1000L)
throw Exception("launched coroutine broke")
}
println("done launching")
42
}
println ("result: $result")
} catch (e: Exception) {
println("Error: ${e.message}")
}
}
I would expect the above to print result: 42
and then, possibly, print the uncaught exception from the child coroutine. Instead it waits for one second and then prints Error: launched coroutine broke
.
The actual behavior, therefore, matches that of the coroutineScope
builder. While it may be a useful behavior, I think it contradicts the documentation. Should the documentation be updated to something similar to coroutineScope
?
This function returns as soon as the given block and all its children coroutines are completed.
Furthermore, does that mean that we can use coroutineScope
and withContext(coroutineContext)
interchangeably, the only difference being a bit less boilerplate?
withContext
creates a new job. This means that all coroutines launched inside are children of this job. It only returns when the job is finished. Because of structured concurrency, it only finishes when all child coroutines are finished too.When any of the child jobs fails, the parent job is canceled. This will also cancel all other child jobs. Since
withContext
returns a result, the exception is thrown.The documentation of
CoroutineScope
is helpful in this regards:I think the documentation of
withContext
could be improved too. The documentation ofJob
andCoroutineContext
are very helpful as they provide a more high-level point of view.Yes, they should behave the same way. They are intended for different use cases though.
coroutineScope
is meant to provide a scope for multiple parallel coroutines in which all will be canceled, if any fails.withContext
is designed to be used to switch the context (eg. the Dispatcher) for the given block of code.Here is a similar question I recently asked on the kotlin discussion forums. The thread contains some more similar cases and further insight.