async
Launches a new child coroutine of CoroutineScope without blocking the current thread and returns a reference to the coroutine as a Deferred that can be used to access the result of the coroutine.
block is the computation of the new coroutine that will run concurrently. The coroutine is considered active until the block and all the child coroutines created in it finish. The result of executing the block is available via the returned Deferred.
context specifies the additional context elements for the coroutine to combine with the elements already present in the CoroutineScope.coroutineContext. It is incorrect to pass a Job element there, as this breaks structured concurrency.
By default, the coroutine is scheduled for execution on its ContinuationInterceptor. There is no guarantee that it will start immediately: this is decided by the ContinuationInterceptor. It is possible that the new coroutine will be cancelled before starting, in which case its code will not be executed. The start parameter can be used to adjust this behavior. See CoroutineStart for details.
Structured Concurrency
Coroutine context
async creates a child coroutine of this CoroutineScope.
See the corresponding subsection in the launch documentation for details on how the coroutine context is created. In essence, the elements of context are combined with the elements of the CoroutineScope.coroutineContext, typically overriding them. It is incorrect to pass a Job element there, as this breaks structured concurrency.
Interactions between coroutines
The details of structured concurrency are described in the CoroutineScope interface documentation. Here is a restatement of some main points as they relate to async:
The lifecycle of the parent CoroutineScope cannot end until this coroutine and all its children complete.
If the parent CoroutineScope is cancelled, this coroutine is cancelled as well.
If this coroutine fails with a non-CancellationException exception and the parent CoroutineScope has a non-supervisor Job in its context, the parent Job is cancelled with this exception.
If this coroutine fails with an exception and the parent CoroutineScope has a supervisor Job or no job at all (as is the case with GlobalScope or malformed scopes), the exception cannot be propagated and is only available through the returned Deferred.
The lifecycle of the CoroutineScope passed as the receiver to the block will not end until the block completes (or gets cancelled before ever having a chance to run).
If the block throws a CancellationException, the coroutine is considered cancelled, cancelling all its children in turn, but the parent does not get notified.
Communicating with the coroutine
Deferred.await can be used to suspend the current coroutine until the result of the async coroutine is available. It returns the result of the block executed in the async coroutine. Note that if the async coroutine fails with an exception, Deferred.await will also throw that exception, including CancellationException if the coroutine was cancelled. See the "CancellationException silently stopping computations" pitfall in the launch documentation.
Deferred.cancel can be used to cancel the coroutine, and Deferred.join suspends until its completion without blocking the current thread or accessing its result. Note that Deferred.join succeeds even if the coroutine was cancelled or failed with an exception. Deferred.cancelAndJoin is a convenience function that combines cancellation and joining.
If the coroutine was started with start set to CoroutineStart.LAZY, the coroutine will not be scheduled to run on its ContinuationInterceptor immediately. Deferred.start can be used to start the coroutine explicitly, and awaiting its result using Deferred.await or awaitAll or its completion using Deferred.join also causes the coroutine to start executing.
Parameters
additional to CoroutineScope.coroutineContext context of the coroutine.
coroutine start option. The default value is CoroutineStart.DEFAULT.
the coroutine code which will be invoked in the child coroutine.
Deprecated
Passing a Job to coroutine builders breaks structured concurrency, leading to hard-to-diagnose errors. This pattern should be avoided. This overload will be deprecated with an error in the future.
Deprecated version of async that accepts a Job.
See the documentation for the non-deprecated async function to learn about the functionality of this function. This piece of documentation explains why this overload is deprecated.
It is incorrect to pass a Job as context to async or launch, because this violates structured concurrency. The passed Job becomes the sole parent of the newly created coroutine, which completely severs the tie between the new coroutine and the CoroutineScope in which it is launched.
Benefits of structured concurrency
Structured concurrency ensures that
Cancellation of the parent job cancels the children as well, which helps avoid unnecessary computations when they are no longer needed.
Cancellation of children also can be necessary for reliability: if the CoroutineScope's lifecycle is bound to some component that may not be used after it's destroyed, performing computations after the parent CoroutineScope is cancelled may lead to crashes.
For concurrent decomposition of work (when the CoroutineScope contains a non-supervisor job), failure of the newly created coroutine also causes the sibling coroutines to fail, improving the responsiveness of the program: unnecessary computations will not proceed when it's obvious that they are not needed.
The CoroutineScope can only complete when all its children complete. If the CoroutineScope is lexically scoped (for example, created by coroutineScope, supervisorScope, or withContext), this means that the lexical scope will only be exited (and the calling function will finish) once all child coroutines complete.
Possible alternatives
In some scenarios, one or more of the properties guaranteed by structured concurrency are actually undesirable. However, breaking structured concurrency altogether and losing the other properties can often be avoided.
Ignoring cancellation
Sometimes, it is undesirable for the child coroutine to react to the cancellation of the parent: for example, some computations have to be performed unconditionally.
Seeing async(NonCancellable) in code is a reliable sign that this was the intention. Alternatively, you may see async(Job()). Both patterns break structured concurrency and prevent cancellation from being propagated.
Here's an alternative approach that preserves structured concurrency:
// Guarantees the completion, but not the delivery of the value
scope.async(start = CoroutineStart.ATOMIC) {
withContext(NonCancellable) {
// The actual body of the coroutine.
// This code will get executed even if the parent is cancelled.
}
// Note: the cancellation exception *can* be thrown here,
// losing the computed value!
}
// Guarantees the delivery of the value, but is more complex
val asyncResult = CompletableDeferred<T>()
scope.launch(start = CoroutineStart.ATOMIC) {
withContext(NonCancellable) {
asyncResult.completeWith(
runCatching {
// compute the value
}
)
}
}This way, the child coroutine is guaranteed to complete, but the scope is still aware of the child. This allows the parent scope to await the completion of the child and to react to its failure.
Not cancelling other coroutines on failure
Often, the failure of one child does not require the work of the other coroutines to be cancelled.
async(SupervisorJob()) is a telling sign that this was the reason for breaking structured concurrency in code, though async(Job()) has the exact same effect. By breaking structured concurrency, async(SupervisorJob()) { error("failure") } will prevent failure from affecting the parent coroutine and the siblings.
If all coroutines in a scope should fail independently, this suggests that the scope is a supervisor:
supervisorScope {
val coroutines = List(10) {
async {
delay(10.milliseconds * it)
throw IllegalStateException("$it is tired of all this")
}
}
coroutines.forEach {
println(runCatching { it.await() })
}
}Every coroutine here will run to completion and will fail with its own error.
For non-lexically-scoped CoroutineScope instances, use SupervisorJob instead of Job when constructing the CoroutineScope.
If only some coroutines need to individually have their failures invisible to others in a non-lexically-scoped CoroutineScope, the correct approach from the point of view of structured concurrency is this:
val supervisorJob = SupervisorJob(scope.coroutineContext.job)
val childSupervisorScope = CoroutineScope(
scope.coroutineContext + supervisorJob
)
childSupervisorScope.async {
// failures in this coroutine will not affect other children
}
// `cancel` or `complete` the `supervisorJob` when it's no longer needed
supervisorJob.complete()
supervisorJob.join()For a lexically scoped CoroutineScope, it may be possible to use a supervisorScope at the end of the outer scope, depending on the code structure:
coroutineScope {
launch {
// failures in this coroutine will affect everyone
}
supervisorScope {
val deferred = async {
// failures in this coroutine
// are only available through `deferred`
}
}
// this line will only be reached when `async` completes
}
// this line will be reached when both `launch` and `async` completeAll of these approaches preserve the ability of a parent to cancel the children and to wait for their completion.
Avoiding both cancelling and being cancelled
Sometimes, coroutines to be spawned are just completely unrelated to the CoroutineScope used as the receiver, and no structured concurrency mechanisms are needed.
In that case, GlobalScope is the semantically clearer way of expressing opting out of structured concurrency:
GlobalScope.async {
// this computation is explicitly outside structured concurrency
}The reason why GlobalScope is marked as delicate is exactly that the coroutines created in it are not benefitting from structured concurrency.
Deprecated
Passing a NonCancellable to `async` breaks structured concurrency, leading to hard-to-diagnose errors. This pattern should be avoided. This overload will be deprecated with an error in the future.
Deprecated version of async that accepts a NonCancellable.
See the documentation for the non-deprecated async function to learn about the functionality of this function. This piece of documentation explains why this overload is deprecated.
Passing NonCancellable to launch or async breaks structured concurrency, completely severing the tie between the new coroutine and the CoroutineScope in which it is launched. This has the effect of preventing the new coroutine from being cancelled when the parent coroutine is cancelled, which is probably what was intended. However, in addition to that, it breaks the other aspects of structured concurrency.
A CoroutineScope only completes when all its children complete. When NonCancellable removes the parent-child tie, the CoroutineScope will not wait for the completion of the new coroutine.
coroutineScope {
async(NonCancellable) {
delay(100.milliseconds)
println("The child only completes now")
}
}
println("The parent completed before the child")Content copied to clipboardA child typically notifies the parent about its failure by cancelling it. When NonCancellable removes the parent-child tie, the child will not be able to notify the parent about its failure. If this is the intended effect, please use SupervisorJob or supervisorScope to ensure independent failure of children.
// `async` will not cancel the `coroutineScope`.
// Instead, the exception will only be available in the resulting `Deferred`.
coroutineScope {
async<Int>(NonCancellable) {
error("The child failed")
}
}Content copied to clipboard
A pattern that prevents child cancellation even when the parent is cancelled consists of two parts:
CoroutineStart.ATOMIC or CoroutineStart.UNDISPATCHED both ensure that the new coroutine is at least going to be started and run until the first suspension, even if the parent is already cancelled.
Using NonCancellable together with withContext in the coroutine's body ensures that the child will successfully resume from suspension points even if the coroutine is cancelled.
Example:
async(start = CoroutineStart.ATOMIC) {
withContext(NonCancellable) {
// Actual coroutine body here
}
}