| /* |
| * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. |
| */ |
| @file:OptIn(ExperimentalContracts::class) |
| |
| package kotlinx.coroutines |
| |
| import kotlinx.coroutines.internal.* |
| import kotlinx.coroutines.intrinsics.* |
| import kotlin.contracts.* |
| import kotlin.coroutines.* |
| import kotlin.coroutines.intrinsics.* |
| |
| /** |
| * Defines a scope for new coroutines. Every **coroutine builder** (like [launch], [async], etc) |
| * is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext] |
| * to automatically propagate all its elements and cancellation. |
| * |
| * The best ways to obtain a standalone instance of the scope are [CoroutineScope()] and [MainScope()] factory functions. |
| * Additional context elements can be appended to the scope using the [plus][CoroutineScope.plus] operator. |
| * |
| * ### Convention for structured concurrency |
| * |
| * Manual implementation of this interface is not recommended, implementation by delegation should be preferred instead. |
| * By convention, the [context of a scope][CoroutineScope.coroutineContext] should contain an instance of a |
| * [job][Job] to enforce the discipline of **structured concurrency** with propagation of cancellation. |
| * |
| * Every coroutine builder (like [launch], [async], etc) |
| * and every scoping function (like [coroutineScope], [withContext], etc) provides _its own_ scope |
| * with its own [Job] instance into the inner block of code it runs. |
| * By convention, they all wait for all the coroutines inside their block to complete before completing themselves, |
| * thus enforcing the structured concurrency. See [Job] documentation for more details. |
| * |
| * ### Android usage |
| * |
| * Android has first-party support for coroutine scope in all entities with the lifecycle. |
| * See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope). |
| * |
| * ### Custom usage |
| * |
| * [CoroutineScope] should be implemented or declared as a property on entities with a well-defined lifecycle that are |
| * responsible for launching children coroutines, for example: |
| * |
| * ``` |
| * class MyUIClass { |
| * val scope = MainScope() // the scope of MyUIClass |
| * |
| * fun destroy() { // destroys an instance of MyUIClass |
| * scope.cancel() // cancels all coroutines launched in this scope |
| * // ... do the rest of cleanup here ... |
| * } |
| * |
| * /* |
| * * Note: if this instance is destroyed or any of the launched coroutines |
| * * in this method throws an exception, then all nested coroutines are cancelled. |
| * */ |
| * fun showSomeData() = scope.launch { // launched in the main thread |
| * // ... here we can use suspending functions or coroutine builders with other dispatchers |
| * draw(data) // draw in the main thread |
| * } |
| * } |
| * ``` |
| */ |
| public interface CoroutineScope { |
| /** |
| * The context of this scope. |
| * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope. |
| * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages. |
| * |
| * By convention, should contain an instance of a [job][Job] to enforce structured concurrency. |
| */ |
| public val coroutineContext: CoroutineContext |
| } |
| |
| /** |
| * Adds the specified coroutine context to this scope, overriding existing elements in the current |
| * scope's context with the corresponding keys. |
| * |
| * This is a shorthand for `CoroutineScope(thisScope + context)`. |
| */ |
| public operator fun CoroutineScope.plus(context: CoroutineContext): CoroutineScope = |
| ContextScope(coroutineContext + context) |
| |
| /** |
| * Creates the main [CoroutineScope] for UI components. |
| * |
| * Example of use: |
| * ``` |
| * class MyAndroidActivity { |
| * private val scope = MainScope() |
| * |
| * override fun onDestroy() { |
| * super.onDestroy() |
| * scope.cancel() |
| * } |
| * } |
| * ``` |
| * |
| * The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements. |
| * If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator: |
| * `val scope = MainScope() + CoroutineName("MyActivity")`. |
| */ |
| @Suppress("FunctionName") |
| public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main) |
| |
| /** |
| * Returns `true` when the current [Job] is still active (has not completed and was not cancelled yet). |
| * |
| * Check this property in long-running computation loops to support cancellation: |
| * ``` |
| * while (isActive) { |
| * // do some computation |
| * } |
| * ``` |
| * |
| * This property is a shortcut for `coroutineContext.isActive` in the scope when |
| * [CoroutineScope] is available. |
| * See [coroutineContext][kotlin.coroutines.coroutineContext], |
| * [isActive][kotlinx.coroutines.isActive] and [Job.isActive]. |
| */ |
| @Suppress("EXTENSION_SHADOWED_BY_MEMBER") |
| public val CoroutineScope.isActive: Boolean |
| get() = coroutineContext[Job]?.isActive ?: true |
| |
| /** |
| * A global [CoroutineScope] not bound to any job. |
| * |
| * Global scope is used to launch top-level coroutines which are operating on the whole application lifetime |
| * and are not cancelled prematurely. |
| * Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them. |
| * |
| * Application code usually should use an application-defined [CoroutineScope]. Using |
| * [async][CoroutineScope.async] or [launch][CoroutineScope.launch] |
| * on the instance of [GlobalScope] is highly discouraged. |
| * |
| * Usage of this interface may look like this: |
| * |
| * ``` |
| * fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) { |
| * for (number in this) { |
| * send(Math.sqrt(number)) |
| * } |
| * } |
| * ``` |
| */ |
| public object GlobalScope : CoroutineScope { |
| /** |
| * Returns [EmptyCoroutineContext]. |
| */ |
| override val coroutineContext: CoroutineContext |
| get() = EmptyCoroutineContext |
| } |
| |
| /** |
| * Creates a [CoroutineScope] and calls the specified suspend block with this scope. |
| * The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides |
| * the context's [Job]. |
| * |
| * This function is designed for _parallel decomposition_ of work. When any child coroutine in this scope fails, |
| * this scope fails and all the rest of the children are cancelled (for a different behavior see [supervisorScope]). |
| * This function returns as soon as the given block and all its children coroutines are completed. |
| * A usage example of a scope looks like this: |
| * |
| * ``` |
| * suspend fun showSomeData() = coroutineScope { |
| * val data = async(Dispatchers.IO) { // <- extension on current scope |
| * ... load some UI data for the Main thread ... |
| * } |
| * |
| * withContext(Dispatchers.Main) { |
| * doSomeWork() |
| * val result = data.await() |
| * display(result) |
| * } |
| * } |
| * ``` |
| * |
| * The scope in this example has the following semantics: |
| * 1) `showSomeData` returns as soon as the data is loaded and displayed in the UI. |
| * 2) If `doSomeWork` throws an exception, then the `async` task is cancelled and `showSomeData` rethrows that exception. |
| * 3) If the outer scope of `showSomeData` is cancelled, both started `async` and `withContext` blocks are cancelled. |
| * 4) If the `async` block fails, `withContext` will be cancelled. |
| * |
| * The method may throw a [CancellationException] if the current job was cancelled externally |
| * or may throw a corresponding unhandled [Throwable] if there is any unhandled exception in this scope |
| * (for example, from a crashed coroutine that was started with [launch][CoroutineScope.launch] in this scope). |
| */ |
| public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R { |
| contract { |
| callsInPlace(block, InvocationKind.EXACTLY_ONCE) |
| } |
| return suspendCoroutineUninterceptedOrReturn { uCont -> |
| val coroutine = ScopeCoroutine(uCont.context, uCont) |
| coroutine.startUndispatchedOrReturn(coroutine, block) |
| } |
| } |
| |
| /** |
| * Creates a [CoroutineScope] that wraps the given coroutine [context]. |
| * |
| * If the given [context] does not contain a [Job] element, then a default `Job()` is created. |
| * This way, cancellation or failure of any child coroutine in this scope cancels all the other children, |
| * just like inside [coroutineScope] block. |
| */ |
| @Suppress("FunctionName") |
| public fun CoroutineScope(context: CoroutineContext): CoroutineScope = |
| ContextScope(if (context[Job] != null) context else context + Job()) |
| |
| /** |
| * Cancels this scope, including its job and all its children with an optional cancellation [cause]. |
| * A cause can be used to specify an error message or to provide other details on |
| * a cancellation reason for debugging purposes. |
| * Throws [IllegalStateException] if the scope does not have a job in it. |
| */ |
| public fun CoroutineScope.cancel(cause: CancellationException? = null) { |
| val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this") |
| job.cancel(cause) |
| } |
| |
| /** |
| * Cancels this scope, including its job and all its children with a specified diagnostic error [message]. |
| * A [cause] can be specified to provide additional details on a cancellation reason for debugging purposes. |
| * Throws [IllegalStateException] if the scope does not have a job in it. |
| */ |
| public fun CoroutineScope.cancel(message: String, cause: Throwable? = null): Unit = cancel(CancellationException(message, cause)) |
| |
| /** |
| * Ensures that current scope is [active][CoroutineScope.isActive]. |
| * Throws [IllegalStateException] if the context does not have a job in it. |
| * |
| * If the job is no longer active, throws [CancellationException]. |
| * If the job was cancelled, thrown exception contains the original cancellation cause. |
| * |
| * This method is a drop-in replacement for the following code, but with more precise exception: |
| * ``` |
| * if (!isActive) { |
| * throw CancellationException() |
| * } |
| * ``` |
| */ |
| public fun CoroutineScope.ensureActive(): Unit = coroutineContext.ensureActive() |
| |
| |
| /** |
| * Returns the current [CoroutineContext] retrieved by using [kotlin.coroutines.coroutineContext]. |
| * This function is an alias to avoid name clash with [CoroutineScope.coroutineContext] in a receiver position: |
| * |
| * ``` |
| * launch { // this: CoroutineScope |
| * val flow = flow<Unit> { |
| * coroutineContext // Resolves into the context of outer launch, which is incorrect, see KT-38033 |
| * currentCoroutineContext() // Retrieves actual context where the flow is collected |
| * } |
| * } |
| * ``` |
| */ |
| public suspend inline fun currentCoroutineContext(): CoroutineContext = coroutineContext |