launch

fun CoroutineScope.launch(context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit): Job(source)

Launches a new child coroutine of CoroutineScope without blocking the current thread and returns a reference to the coroutine as a Job.

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.

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

launch creates a child coroutine of this CoroutineScope.

The context of the new coroutine is created like this:

The resulting coroutine context becomes the coroutineContext of the CoroutineScope passed to the block as its receiver.

The new coroutine is considered active until the block and all its child coroutines finish. If the block throws a CancellationException, the coroutine is considered cancelled, and if it throws any other exception, the coroutine is considered failed.

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 launch:

Overriding the parent job

Passing a Job in the context argument breaks structured concurrency and is not a supported pattern. It does not throw an exception only for backward compatibility reasons, as a lot of code was written this way. Always structure your coroutines so that the lifecycle of the child coroutine is contained in the lifecycle of the CoroutineScope it is launched in.

To help with migrating to structured concurrency, the specific behavior of passing a Job in the context argument is described here. Do not rely on this behavior in new code.

If context contains a Job element, it will be the parent of the new coroutine, and the lifecycle of the new coroutine will not be tied to the CoroutineScope at all.

In specific terms:

  • If the CoroutineScope is cancelled, the new coroutine will not be affected.

  • If the new coroutine fails with an exception, it will not cancel the CoroutineScope. Instead, the exception will be propagated to the Job passed in the context argument. If that Job is a SupervisorJob, the exception will be unhandled, and will be propagated as the CoroutineExceptionHandler documentation describes. If that Job is not a SupervisorJob, it will be cancelled with the exception the coroutine failed with.

  • The CoroutineScope may complete without waiting for the new coroutine to finish. In particular, if the CoroutineScope is lexically scoped (for example, created by coroutineScope or withContext), the function defining the scope will not wait for the new coroutine to finish.

Communicating with the coroutine

Job.cancel can be used to cancel the coroutine, and Job.join suspends until its completion without blocking the current thread. Note that Job.join succeeds even if the coroutine was cancelled or failed with an exception. Job.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. Job.start can be used to start the coroutine explicitly, and awaiting its completion using Job.join also causes the coroutine to start executing.

A coroutine created with launch does not return a result, and if it fails with an exception, there is no reliable way to learn about that exception in general. async is a better choice if the result of the coroutine needs to be accessed from another coroutine.

Differences from async

launch is similar to async whose block returns a Unit value.

The only difference is the handling of failures that cannot be propagated to the parent: for an async coroutine, a CoroutineExceptionHandler will not be invoked in that scenario. Instead, the user of async must call Deferred.await to get the result of the coroutine, which will be the exception the coroutine failed with.

Pitfalls

CancellationException silently stopping computations

val deferred = GlobalScope.async {
awaitCancellation()
}
deferred.cancel()
coroutineScope {
val job = launch {
val result = deferred.await()
println("Got $result")
}
job.join()
println("Am I still not cancelled? $isActive")
}

will output

Am I still not cancelled? true

This may be surprising, because the launched coroutine failed with an exception, but the parent still was not cancelled.

The reason for this is that any CancellationException thrown in the coroutine is treated as a signal to cancel the coroutine, but not the parent. In this scenario, this is unlikely to be the desired behavior: this was a failure and not a cancellation and should be propagated to the parent.

This is a legacy behavior that cannot be changed in a backward-compatible way. Use ensureActive and isActive to distinguish between cancellation and failure:

launch {
try {
val result = deferred.await()
} catch (e: CancellationException) {
if (isActive) {
// we were not cancelled, this is a failure
println("`deferred` was cancelled")
throw IllegalStateException("deferred was cancelled", e)
} else {
println("I was cancelled")
// throw again to finish the coroutine
ensureActive()
}
}
}

In simpler scenarios, this form can be used:

launch {
try {
// operation that may throw its own CancellationException
} catch (e: CancellationException) {
ensureActive()
throw IllegalStateException(e)
}
}

Parameters

context

additional to CoroutineScope.coroutineContext context of the coroutine.

start

coroutine start option. The default value is CoroutineStart.DEFAULT.

block

the coroutine code which will be invoked in the child coroutine.


fun CoroutineScope.launch(context: Job, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit): Job(source)

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 launch that accepts a Job.

See the documentation for the non-deprecated launch 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 launch or async, 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 launch(NonCancellable) in code is a reliable sign that this was the intention. Alternatively, you may see launch(Job()). Both patterns break structured concurrency and prevent cancellation from being propagated.

Here's an alternative approach that preserves structured concurrency:

scope.launch(start = CoroutineStart.ATOMIC) {
withContext(NonCancellable) {
// this line will be reached even if the parent is cancelled
}
}

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.

launch(SupervisorJob()) is a telling sign that this was the reason for breaking structured concurrency in code, though launch(Job()) has the exact same effect. By breaking structured concurrency, launch(SupervisorJob()) { error("failure") } will prevent failure from affecting the parent coroutine and the siblings.

Occasionally, the failure of one child does not mean the work of the other children is also unneeded. launch(Job()) { failure() } makes sure that the only effect of failure() is to make this launch finish with an error, while the other coroutines continue executing.

If all coroutines in a scope should fail independently, this suggests that the scope is a supervisor:

withContext(CoroutineExceptionHandler { _, e ->
println("Failure($e)")
}) {
supervisorScope {
val coroutines = List(10) {
launch {
delay(10.milliseconds * it)
throw IllegalStateException("$it is tired of all this")
}
}
coroutines.joinAll() // errors in `launch` don't affect this call!
}
}

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 +
CoroutineExceptionHandler { _, e ->
// process errors
}
)
childSupervisorScope.launch {
// 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 {
val deferred = async {
// failures in this coroutine will affect everyone
}
supervisorScope {
val job = launch(
CoroutineExceptionHandler { _, e ->
// some individual mechanism of processing exceptions
}
) {
// failures in this coroutine
// are only available through `job`
}
}
// this line will only be reached when `launch` completes
}
// this line will be reached when both `async` and `launch` complete

All 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.launch(CoroutineExceptionHandler { _, e ->
/* what to do if this coroutine fails */
}) {
// 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.


fun CoroutineScope.launch(context: NonCancellable, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit): Job(source)

Deprecated

Passing a NonCancellable to `launch` 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 launch that accepts a NonCancellable.

See the documentation for the non-deprecated launch 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.

  1. 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 {
    launch(NonCancellable) {
    delay(100.milliseconds)
    println("The child only completes now")
    }
    }
    println("The parent completed before the child")
  2. A 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.

     // `launch` will not cancel the `coroutineScope`.
    // Instead, without a propagation path, the exception will be passed
    // to `CoroutineExceptionHandler` and potentially crash the program.
    coroutineScope {
    launch(NonCancellable) {
    error("The child failed")
    }
    }

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:

launch(start = CoroutineStart.ATOMIC) {
withContext(NonCancellable) {
// Actual coroutine body here
}
}