• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 @file:OptIn(ExperimentalContracts::class)
5 
6 package kotlinx.coroutines
7 
8 import kotlinx.coroutines.internal.*
9 import kotlinx.coroutines.intrinsics.*
10 import kotlin.contracts.*
11 import kotlin.coroutines.*
12 import kotlin.coroutines.intrinsics.*
13 
14 /**
15  * Defines a scope for new coroutines. Every **coroutine builder** (like [launch], [async], etc)
16  * is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
17  * to automatically propagate all its elements and cancellation.
18  *
19  * The best ways to obtain a standalone instance of the scope are [CoroutineScope()] and [MainScope()] factory functions.
20  * Additional context elements can be appended to the scope using the [plus][CoroutineScope.plus] operator.
21  *
22  * ### Convention for structured concurrency
23  *
24  * Manual implementation of this interface is not recommended, implementation by delegation should be preferred instead.
25  * By convention, the [context of a scope][CoroutineScope.coroutineContext] should contain an instance of a
26  * [job][Job] to enforce the discipline of **structured concurrency** with propagation of cancellation.
27  *
28  * Every coroutine builder (like [launch], [async], etc)
29  * and every scoping function (like [coroutineScope], [withContext], etc) provides _its own_ scope
30  * with its own [Job] instance into the inner block of code it runs.
31  * By convention, they all wait for all the coroutines inside their block to complete before completing themselves,
32  * thus enforcing the structured concurrency. See [Job] documentation for more details.
33  *
34  * ### Android usage
35  *
36  * Android has first-party support for coroutine scope in all entities with the lifecycle.
37  * See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope).
38  *
39  * ### Custom usage
40  *
41  * [CoroutineScope] should be implemented or declared as a property on entities with a well-defined lifecycle that are
42  * responsible for launching children coroutines, for example:
43  *
44  * ```
45  * class MyUIClass {
46  *     val scope = MainScope() // the scope of MyUIClass
47  *
48  *     fun destroy() { // destroys an instance of MyUIClass
49  *         scope.cancel() // cancels all coroutines launched in this scope
50  *         // ... do the rest of cleanup here ...
51  *     }
52  *
53  *     /*
54  *      * Note: if this instance is destroyed or any of the launched coroutines
55  *      * in this method throws an exception, then all nested coroutines are cancelled.
56  *      */
57  *     fun showSomeData() = scope.launch { // launched in the main thread
58  *        // ... here we can use suspending functions or coroutine builders with other dispatchers
59  *        draw(data) // draw in the main thread
60  *     }
61  * }
62  * ```
63  */
64 public interface CoroutineScope {
65     /**
66      * The context of this scope.
67      * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
68      * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
69      *
70      * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
71      */
72     public val coroutineContext: CoroutineContext
73 }
74 
75 /**
76  * Adds the specified coroutine context to this scope, overriding existing elements in the current
77  * scope's context with the corresponding keys.
78  *
79  * This is a shorthand for `CoroutineScope(thisScope + context)`.
80  */
plusnull81 public operator fun CoroutineScope.plus(context: CoroutineContext): CoroutineScope =
82     ContextScope(coroutineContext + context)
83 
84 /**
85  * Creates the main [CoroutineScope] for UI components.
86  *
87  * Example of use:
88  * ```
89  * class MyAndroidActivity {
90  *     private val scope = MainScope()
91  *
92  *     override fun onDestroy() {
93  *         super.onDestroy()
94  *         scope.cancel()
95  *     }
96  * }
97  * ```
98  *
99  * The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
100  * If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
101  * `val scope = MainScope() + CoroutineName("MyActivity")`.
102  */
103 @Suppress("FunctionName")
104 public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
105 
106 /**
107  * Returns `true` when the current [Job] is still active (has not completed and was not cancelled yet).
108  *
109  * Check this property in long-running computation loops to support cancellation:
110  * ```
111  * while (isActive) {
112  *     // do some computation
113  * }
114  * ```
115  *
116  * This property is a shortcut for `coroutineContext.isActive` in the scope when
117  * [CoroutineScope] is available.
118  * See [coroutineContext][kotlin.coroutines.coroutineContext],
119  * [isActive][kotlinx.coroutines.isActive] and [Job.isActive].
120  */
121 @Suppress("EXTENSION_SHADOWED_BY_MEMBER")
122 public val CoroutineScope.isActive: Boolean
123     get() = coroutineContext[Job]?.isActive ?: true
124 
125 /**
126  * A global [CoroutineScope] not bound to any job.
127  *
128  * Global scope is used to launch top-level coroutines which are operating on the whole application lifetime
129  * and are not cancelled prematurely.
130  * Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them.
131  *
132  * Application code usually should use an application-defined [CoroutineScope]. Using
133  * [async][CoroutineScope.async] or [launch][CoroutineScope.launch]
134  * on the instance of [GlobalScope] is highly discouraged.
135  *
136  * Usage of this interface may look like this:
137  *
138  * ```
139  * fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {
140  *     for (number in this) {
141  *         send(Math.sqrt(number))
142  *     }
143  * }
144  * ```
145  */
146 public object GlobalScope : CoroutineScope {
147     /**
148      * Returns [EmptyCoroutineContext].
149      */
150     override val coroutineContext: CoroutineContext
151         get() = EmptyCoroutineContext
152 }
153 
154 /**
155  * Creates a [CoroutineScope] and calls the specified suspend block with this scope.
156  * The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides
157  * the context's [Job].
158  *
159  * This function is designed for _parallel decomposition_ of work. When any child coroutine in this scope fails,
160  * this scope fails and all the rest of the children are cancelled (for a different behavior see [supervisorScope]).
161  * This function returns as soon as the given block and all its children coroutines are completed.
162  * A usage example of a scope looks like this:
163  *
164  * ```
165  * suspend fun showSomeData() = coroutineScope {
166  *     val data = async(Dispatchers.IO) { // <- extension on current scope
167  *      ... load some UI data for the Main thread ...
168  *     }
169  *
170  *     withContext(Dispatchers.Main) {
171  *         doSomeWork()
172  *         val result = data.await()
173  *         display(result)
174  *     }
175  * }
176  * ```
177  *
178  * The scope in this example has the following semantics:
179  * 1) `showSomeData` returns as soon as the data is loaded and displayed in the UI.
180  * 2) If `doSomeWork` throws an exception, then the `async` task is cancelled and `showSomeData` rethrows that exception.
181  * 3) If the outer scope of `showSomeData` is cancelled, both started `async` and `withContext` blocks are cancelled.
182  * 4) If the `async` block fails, `withContext` will be cancelled.
183  *
184  * The method may throw a [CancellationException] if the current job was cancelled externally
185  * or may throw a corresponding unhandled [Throwable] if there is any unhandled exception in this scope
186  * (for example, from a crashed coroutine that was started with [launch][CoroutineScope.launch] in this scope).
187  */
coroutineScopenull188 public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
189     contract {
190         callsInPlace(block, InvocationKind.EXACTLY_ONCE)
191     }
192     return suspendCoroutineUninterceptedOrReturn { uCont ->
193         val coroutine = ScopeCoroutine(uCont.context, uCont)
194         coroutine.startUndispatchedOrReturn(coroutine, block)
195     }
196 }
197 
198 /**
199  * Creates a [CoroutineScope] that wraps the given coroutine [context].
200  *
201  * If the given [context] does not contain a [Job] element, then a default `Job()` is created.
202  * This way, cancellation or failure of any child coroutine in this scope cancels all the other children,
203  * just like inside [coroutineScope] block.
204  */
205 @Suppress("FunctionName")
CoroutineScopenull206 public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
207     ContextScope(if (context[Job] != null) context else context + Job())
208 
209 /**
210  * Cancels this scope, including its job and all its children with an optional cancellation [cause].
211  * A cause can be used to specify an error message or to provide other details on
212  * a cancellation reason for debugging purposes.
213  * Throws [IllegalStateException] if the scope does not have a job in it.
214  */
215 public fun CoroutineScope.cancel(cause: CancellationException? = null) {
216     val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this")
217     job.cancel(cause)
218 }
219 
220 /**
221  * Cancels this scope, including its job and all its children with a specified diagnostic error [message].
222  * A [cause] can be specified to provide additional details on a cancellation reason for debugging purposes.
223  * Throws [IllegalStateException] if the scope does not have a job in it.
224  */
cancelnull225 public fun CoroutineScope.cancel(message: String, cause: Throwable? = null): Unit = cancel(CancellationException(message, cause))
226 
227 /**
228  * Ensures that current scope is [active][CoroutineScope.isActive].
229  *
230  * If the job is no longer active, throws [CancellationException].
231  * If the job was cancelled, thrown exception contains the original cancellation cause.
232  * This function does not do anything if there is no [Job] in the scope's [coroutineContext][CoroutineScope.coroutineContext].
233  *
234  * This method is a drop-in replacement for the following code, but with more precise exception:
235  * ```
236  * if (!isActive) {
237  *     throw CancellationException()
238  * }
239  * ```
240  *
241  * @see CoroutineContext.ensureActive
242  */
243 public fun CoroutineScope.ensureActive(): Unit = coroutineContext.ensureActive()
244 
245 
246 /**
247  * Returns the current [CoroutineContext] retrieved by using [kotlin.coroutines.coroutineContext].
248  * This function is an alias to avoid name clash with [CoroutineScope.coroutineContext] in a receiver position:
249  *
250  * ```
251  * launch { // this: CoroutineScope
252  *     val flow = flow<Unit> {
253  *         coroutineContext // Resolves into the context of outer launch, which is incorrect, see KT-38033
254  *         currentCoroutineContext() // Retrieves actual context where the flow is collected
255  *     }
256  * }
257  * ```
258  */
259 public suspend inline fun currentCoroutineContext(): CoroutineContext = coroutineContext
260