CoroutineScope

A scope in which coroutines run.

A coroutine scope allows managing the lifecycles of several coroutines simultaneously and setting the execution properties with which coroutines (its "children") are launched.

Execution properties are CoroutineContext.Element values that may affect the behavior of kotlinx.coroutines—for example, which thread pool a coroutine should run on. See a more detailed explanation of coroutine context elements in a separate section below.

A set of rules called "structured concurrency" ensures that the lifecycles of children are nested inside the lifecycles of their parent scopes. For example, if a scope is cancelled, all coroutines in it are cancelled too, and the scope itself cannot be completed until all its children are completed. See a more detailed explanation of structured concurrency in a separate section below.

Using coroutine scopes

The methods of this interface are not intended to be called directly. Instead, a CoroutineScope is passed as a receiver to the coroutine builders such as launch and async and affects the execution properties and lifetimes of the created coroutines.

Coroutine context elements

A CoroutineScope is defined by a set of CoroutineContext elements, one of which is typically a Job, described in the section on structured concurrency and responsible for managing lifetimes of coroutines.

Other coroutine context elements include, but are not limited to, the following:

  • The scheduling policy, represented by a CoroutineDispatcher element. Some commonly used dispatchers are provided in the Dispatchers object.

  • CoroutineExceptionHandler that defines how to handle coroutine failures that cannot be propagated to any other coroutine.

  • A CoroutineName element that can be used to name coroutines for debugging purposes.

  • On the JVM, a ThreadContextElement ensures that a specific thread-local value gets set on the thread that executes the coroutine.

Obtaining a coroutine scope

Manual implementations of this interface are not recommended. Instead, a CoroutineScope should be obtained in a way that reflects the intended structured concurrency relationships.

Lexical scopes

coroutineScope and supervisorScope functions can be called in any suspend function to define a scope lexically, ensuring that all coroutines launched in this scope complete by the time the scope-limiting function exits.

suspend fun doSomething() = coroutineScope { // scope `A`
repeat(5) { outer ->
// spawn a new coroutine in the scope `A`
launch {
println("Coroutine $outer started")
coroutineScope { // scope `B`, separate for each `outer` coroutine
repeat(5) { inner ->
// spawn a new coroutine in the scope `B`
launch {
println("Coroutine $outer.$inner started")
delay(10.milliseconds)
println("Coroutine $outer.$inner finished")
}
}
}
// will only exit once all `Coroutine $outer.X finished` messages are printed
println("Coroutine $outer finished")
}
}
} // will only exit once all `Coroutine X finished` messages are printed

This is the preferred way to create a CoroutineScope.

CoroutineScope constructor function

When the lifecycle of the scope is not limited lexically (for example, when coroutines should outlive the function that creates them) but is tied to the lifecycle of some entity, the CoroutineScope constructor function can be used to define a personal scope for the entity. This scope should be stored as a field in the entity.

The key part of using a custom CoroutineScope is cancelling it at the end of the lifecycle. The CoroutineScope.cancel extension function shall be used when the entity launching coroutines is no longer needed. It cancels all the coroutines that might still be running on its behalf.

class MyEntity(scope: CoroutineScope? = null): AutoCloseable {
// careful: do not write `get() =` here by accident!
private val scope = scope ?: CoroutineScope(SupervisorJob() + CoroutineExceptionHandler { _, e ->
println("Error in coroutine: $e")
})

fun doSomethingWhileEntityExists() = scope.launch {
while (true) {
// do some work
delay(50.milliseconds)
println("Doing something")
}
}

override fun close() {
// cancel all computations related to this entity
scope.cancel()
}
}

fun main() {
MyEntity().use { entity ->
entity.doSomethingWhileEntityExists()
Thread.sleep(200)
}
}

Usually, a custom CoroutineScope should be created with a SupervisorJob and a CoroutineExceptionHandler to handle exceptions in child coroutines. See the documentation for the CoroutineScope constructor function for more details. Also note that MyEntity accepts the scope parameter that can be used to pass a custom scope for testing.

Sometimes, coroutine-aware frameworks provide CoroutineScope instances like this out of the box. For example, on Android, all entities with a lifecycle and all ViewModel instances expose a CoroutineScope: see the corresponding documentation.

Taking another view of an existing scope

Occasionally, several coroutines need to be launched with the same additional CoroutineContext that is not present in the original scope. In this case, the CoroutineScope.plus operator can be used to create a new view of an existing scope:

coroutineScope {
val sameScopeButInUiThread = this + Dispatchers.Main
sameScopeButInUiThread.launch {
println("Running on the main thread")
}
launch {
println("This will run using the original dispatcher")
}
sameScopeButInUiThread.launch {
println("And this will also run on the main thread")
}
}

The lifecycle of the new scope is the same as the original one, but the context includes new elements.

Application lifecycle scope

GlobalScope is a CoroutineScope that has the lifetime of the whole application. Although it is convenient for launching top-level coroutines that are not tied to the lifecycle of any entity, it is easy to misuse it and create memory or resource leaks when a coroutine actually should be tied to the lifecycle of some entity.

GlobalScope.launch(CoroutineExceptionHandler { _, e ->
println("Error in coroutine: $e")
}) {
while (true) {
println("I will be running forever, you cannot stop me!")
delay(1.seconds)
}
}

by-delegation

When the approaches listed above are not applicable and a custom CoroutineScope implementation is needed, it is recommended to use by-delegation to implement the interface:

class MyEntity : CoroutineScope by CoroutineScope(
SupervisorJob() + Dispatchers.Main + CoroutineExceptionHandler { _, e ->
println("Error in coroutine: $e")
}
)

Structured concurrency in detail

Overview

Structured concurrency is an approach to concurrent programming that attempts to clarify the lifecycles of concurrent operations and to make them easier to reason about.

Skim the following motivating example:

suspend fun downloadFile(url: String): ByteArray {
return withContext(Dispatchers.IO) {
// this code will execute on a thread for blocking work
val file = byteArrayOf()
// download the file
file
}
}

suspend fun downloadAndCompareTwoFiles() {
coroutineScope {
val file1 = async {
// if this fails, everything else quickly fails too
downloadFile("http://example.com/file1")
}
val file2 = async {
downloadFile("http://example.com/file2")
}
launch(Dispatchers.Main) {
// create a separate coroutine on the UI thread
if (file1.await().contentEquals(file2.await())) {
uiShow("Files are equal")
} else {
uiShow("Files are not equal")
}
}
}
// this line will only run once all the coroutines created above
// finish their work or get cancelled
}

In this example, two asynchronous operations are launched in parallel to download two files. If one of the files fails to download, the other one is cancelled too, and the whole operation fails. The coroutineScope function will not return until all the coroutines inside it are completed or cancelled. In addition, it is possible to cancel the coroutine calling downloadAndCompareTwoFiles, and all the coroutines inside it will be cancelled too.

Without structured concurrency, ensuring that no resource leaks occur by the end of the operation and that the operation responds promptly to failure and cancellation requests is challenging. With structured concurrency, this orchestration is done automatically by the coroutine library, and it is enough to specify the relationships between operations declaratively, as shown in the example, without being overwhelmed by intricate inter-thread communications.

Specifics

Coroutines and CoroutineScope instances have an associated lifecycle. A runtime representation of a lifecycle in kotlinx.coroutines is called a Job. Job instances form a hierarchy of parent-child relationships, and the Job of every coroutine spawned in a CoroutineScope is a child of the Job of that scope. This is often shortened to saying that the coroutine is the scope's child.

See the Job documentation for a detailed explanation of the lifecycle stages.

coroutineScope {
val job = coroutineContext[Job]
val childJob = launch { }
check(job === childJob.parent)
}

Because every coroutine has a lifecycle represented by a Job, a CoroutineScope can be associated with it. Most coroutine builders in kotlinx.coroutines expose the CoroutineScope of the coroutine on creation:

coroutineScope { // this block has a `CoroutineScope` receiver
val parentScope = this
var grandChildFinished = false
val childJob = launch {
// this block has a `CoroutineScope` receiver, too
val childScope = this
check(childScope.coroutineContext[Job]?.parent
=== parentScope.coroutineContext[Job])
launch {
// This block also has a `CoroutineScope` receiver!
val grandChildScope = this
check(grandChildScope.coroutineContext[Job]?.parent
=== childScope.coroutineContext[Job])
delay(100.milliseconds)
grandChildFinished = true
}
// Because the grandchild coroutine
// is a child of the child coroutine,
// the child coroutine will not complete
// until the grandchild coroutine does.
}
// Await completion of the child coroutine,
// and therefore the grandchild coroutine too.
childJob.join()
check(grandChildFinished)
}

Such a CoroutineScope receiver is provided for launch, async, and other coroutine builders, as well as for lexically scoping functions like coroutineScope, supervisorScope, and withContext. Each of these CoroutineScope instances is tied to the lifecycle of the code block it runs in.

Like the example above shows, a coroutine does not complete until all its children are completed. This means that Job.join on a launch or async result or Deferred.await on an async result will not return until all the children of that coroutine are completed. Likewise, lexically scoping functions like coroutineScope and withContext will not return until all the coroutines launched in them are completed.

Interactions between coroutines

See the Job documentation for a detailed explanation of interactions between Job values. Below is a summary of the most important points for structuring code in day-to-day usage.

A coroutine cannot reach the final state until all its children have reached their final states. See the example above.

If a CoroutineScope is cancelled (either explicitly or because it corresponds to some coroutine that failed with an exception), all its children are cancelled too:

val scope = CoroutineScope(
SupervisorJob() + CoroutineExceptionHandler { _, e -> }
)
val job = scope.launch {
// this coroutine will be cancelled
awaitCancellation()
}
scope.cancel() // comment this out for the line below to hang
job.join() // finishes normally

A failure of a child coroutine causes the parent to fail with the same exception if all of the following conditions are met:

  1. The exception is not a CancellationException.

  2. The failed child coroutine was not created with lexically scoped coroutine builders like coroutineScope or withContext.

  3. The parent coroutine's Job is not a SupervisorJob.

The same logic applies recursively to the parent of the parent, etc. Example:

check(
runCatching {
coroutineScope {
launch {
// This cancels the `coroutineScope` coroutine, since
// 1. The coroutine fails with a non-`CancellationException` exception,
// 2. `launch` is not a lexically scoped coroutine builder,
// 3. `coroutineScope` has a non-supervisor `Job`
throw IllegalStateException()
}
launch {
// this coroutine will be cancelled
// when the parent gets cancelled
awaitCancellation()
}
}
}.exceptionOrNull()
is IllegalStateException
)
// The currently running coroutine will *not* be cancelled
// because the failed coroutine (`coroutineScope`) is lexically scoped.
check(currentCoroutineContext().isActive)

Child jobs can lead to the failure of the parent even if the parent has already finished its work and was ready to return a value:

val deferred = GlobalScope.async {
launch {
delay(100.milliseconds)
throw IllegalStateException()
}
10 // this value will be lost!
}
check(
runCatching { deferred.await() }.exceptionOrNull()
is IllegalStateException
)

If several coroutines fail with non-CancellationException exceptions, the first observed failure will be propagated, and the rest will be attached to it as suppressed exceptions.

Failing with a CancellationException only cancels the coroutine itself and its children. It does not affect the parent or sibling coroutines and is not considered a failure.

How-to: stop failures of child coroutines from cancelling other coroutines

If not affecting the CoroutineScope on a failure in a child coroutine is the desired behaviour, then a SupervisorJob should be used instead of Job() when constructing the scope:

val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineExceptionHandler { _, e ->
println("Coroutine failed with exception $e")
})
val failingCoroutine = scope.launch(Dispatchers.IO) {
throw IllegalStateException("This exception will not cancel the scope")
}
failingCoroutine.join()
scope.launch(Dispatchers.IO) {
println("This coroutine is active! See: ${isActive}")
}

Likewise, supervisorScope can replace coroutineScope:

supervisorScope {
val failingCoroutine = launch(CoroutineExceptionHandler { _, e ->
println("Coroutine failed with exception $e")
}) {
throw IllegalStateException("This exception will not cancel the scope")
}
failingCoroutine.join()
launch {
println("This coroutine is active! See: ${isActive}")
}
}

How-to: prevent a child coroutine from being cancelled

Sometimes, you may want to run a coroutine even if the parent coroutine is cancelled. This pattern provides a way to achieve that:

scope.launch(start = CoroutineStart.ATOMIC) {
// Do not move `NonCancellable` to the `context` argument of `launch`!
withContext(NonCancellable) {
// This code will run even if the parent coroutine is cancelled
}
}

CoroutineStart.ATOMIC ensures that the new coroutine is not cancelled until it at least started to execute. NonCancellable in withContext ensures that the code inside the block is executed even if the coroutine created by launch is cancelled.

Inheritors

Properties

Link copied to clipboard

The context of this scope.

Link copied to clipboard

Returns true when the Job of this CoroutineScope is still active (has not completed and was not cancelled yet).

Functions

Link copied to clipboard
fun <E> CoroutineScope.actor(context: CoroutineContext = EmptyCoroutineContext, capacity: Int = 0, start: CoroutineStart = CoroutineStart.DEFAULT, onCompletion: CompletionHandler? = null, block: suspend ActorScope<E>.() -> Unit): SendChannel<E>

Launches new coroutine that is receiving messages from its mailbox channel and returns a reference to its mailbox channel as a SendChannel. The resulting object can be used to send messages to this coroutine.

Link copied to clipboard
fun <T> CoroutineScope.async(context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T): Deferred<T>

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.

fun <T> CoroutineScope.async(context: Job, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T): Deferred<T>

Deprecated version of async that accepts a Job.

fun <T> CoroutineScope.async(context: NonCancellable, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T): Deferred<T>

Deprecated version of async that accepts a NonCancellable.

Link copied to clipboard

Cancels this scope, including its job and all its children with an optional cancellation cause.

fun CoroutineScope.cancel(message: String, cause: Throwable? = null)

Cancels this scope with a CancellationException with the given message and cause.

Link copied to clipboard

Throws the CancellationException that was the scope's cancellation cause if the scope is no longer active.

Link copied to clipboard
fun <T> CoroutineScope.future(context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T): CompletableFuture<T>

Launches a new child coroutine of CoroutineScope without blocking the current thread and returns a CompletableFuture representing the result of the coroutine's execution.

fun <T> CoroutineScope.future(context: Job, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T): CompletableFuture<T>

Deprecated version of future that accepts a Job.

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

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

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

Deprecated version of launch that accepts a Job.

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

Deprecated version of launch that accepts a NonCancellable.

Link copied to clipboard

Creates a context for a new coroutine.

Creates a context for a new coroutine.

Creates a context for a new coroutine.

Creates a context for a new coroutine.

Link copied to clipboard

Creates a new view of this CoroutineScope, but with the specified context added to it.

Link copied to clipboard
fun <E> CoroutineScope.produce(context: CoroutineContext = EmptyCoroutineContext, capacity: Int = Channel.RENDEZVOUS, block: suspend ProducerScope<E>.() -> Unit): ReceiveChannel<E>

Launches a new coroutine to produce a stream of values by sending them to a channel and returns a reference to the coroutine as a ReceiveChannel. This resulting object can be used to receive elements produced by this coroutine.

fun <E> CoroutineScope.produce(context: Job, capacity: Int = Channel.RENDEZVOUS, block: suspend ProducerScope<E>.() -> Unit): ReceiveChannel<E>

Deprecated version of produce that accepts a Job.

Link copied to clipboard
fun <T> CoroutineScope.promise(context: Job, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T): Promise<T>

Deprecated version of promise that accepts a Job.

fun <T : JsAny?> CoroutineScope.promise(context: Job, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T): Promise<T>

Deprecated version of promise that accepts a Job.

fun <T : JsAny?> CoroutineScope.promise(context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T): Promise<T>

Starts new coroutine and returns its result as an implementation of Promise.