coroutineScope
Runs the given block in-place in a new CoroutineScope based on the caller coroutine context, returning its result.
The lifecycle of the new Job begins with starting the block and completes when both the block and all the coroutines launched in the scope complete. Only then can the coroutineScope call return a value.
The context of the new scope is obtained by combining the currentCoroutineContext with a new Job whose parent is the Job of the caller currentCoroutineContext (if any). This parent-child relationship ensures that whenever the caller gets cancelled, so does the new scope.
The Job of the new scope is not a normal child of the caller coroutine but a lexically scoped one, meaning that the failure of the Job will not affect the parent Job. Instead, the exception leading to the failure will be rethrown to the caller of this function.
If block or any child coroutine in this scope fails with an exception, the scope fails, cancelling all the other children and its own block. See supervisorScope for a similar function that allows child coroutines to fail independently.
coroutineScope is suitable for representing a task that can be split into several subtasks, which can be executed concurrently but have their results combined at some point, all in the span of running a single function:
// If the current coroutine is cancelled, `firstFile`, `secondFile`,
// and `await()` get cancelled.
suspend fun downloadAndCompareTwoFiles() = coroutineScope {
val firstFile = async {
// If this fails, `secondFile` and `await()` get cancelled,
// and `downloadAndCompareTwoFiles` rethrows the exception,
// but does not cancel the calling coroutine,
// giving it a chance to recover
downloadFile("1.txt")
}
val secondFile = async {
downloadFile("2.txt")
}
firstFile.await().contentEquals(secondFile.await())
}There is a prompt cancellation guarantee: even if this function is ready to return the result, but was cancelled while suspended, CancellationException will be thrown. See suspendCancellableCoroutine for low-level details.
Pitfall: returning closeable resources from a lexically scoped coroutine
The returned value must be safe to drop without any extra cleanup. For example, this code is incorrect:
// DO NOT DO THIS
val closeableResource = coroutineScope {
// calculate the closeable resource somehow
obtainResource()
}
closeableResource.use { resource ->
// use the resource
}The problem is that, if the caller gets cancelled before coroutineScope completes, then even if the calculation of the closeable resource does not suspend at all, coroutineScope will throw CancellationException instead of returning any value.
This pitfall applies to all coroutineScope-like functions, like withContext, withTimeout, or supervisorScope. For this discussion, we call them collectively myLexicalScope.
If it is necessary to process a value returned from a lexical coroutine scope, the following pattern should be used:
var resource: MyResource? = null
try {
// note: do not simply return the resource here!
myLexicalScope {
resource = obtainResource()
}
// the resource is available here
} finally {
resource?.close()
}If cancellation during the acquisition of the resource is also undesired, the following pattern can be used:
withContext(NonCancellable) {
myLexicalScope {
obtainResource()
}
}.use { resource ->
}See NonCancellable for details.
Be aware, however, that like any NonCancellable usage, this creates the risk of accessing values past the point where they are valid. For example, if the caller coroutine scope is tied to the lifecycle of a UI element, with cancellation meaning that the UI element was already disposed of, accessing the UI during the acquisition of a resource or before the first suspension point in use is not allowed and may lead to crashes.