diff options
Diffstat (limited to 'kotlinx-coroutines-core/common/src/CoroutineScope.kt')
-rw-r--r-- | kotlinx-coroutines-core/common/src/CoroutineScope.kt | 96 |
1 files changed, 83 insertions, 13 deletions
diff --git a/kotlinx-coroutines-core/common/src/CoroutineScope.kt b/kotlinx-coroutines-core/common/src/CoroutineScope.kt index e7c243a4..627318f6 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineScope.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineScope.kt @@ -16,7 +16,10 @@ import kotlin.coroutines.intrinsics.* * 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. + * The best ways to obtain a standalone instance of the scope are [CoroutineScope()] and [MainScope()] factory functions, + * taking care to cancel these coroutine scopes when they are no longer needed (see section on custom usage below for + * explanation and example). + * * Additional context elements can be appended to the scope using the [plus][CoroutineScope.plus] operator. * * ### Convention for structured concurrency @@ -38,12 +41,23 @@ import kotlin.coroutines.intrinsics.* * * ### 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: + * `CoroutineScope` should be declared as a property on entities with a well-defined lifecycle that are + * responsible for launching children coroutines. The corresponding instance of `CoroutineScope` shall be created + * with either `CoroutineScope()` or `MainScope()` functions. The difference between them is only in the + * [CoroutineDispatcher]: + * + * * `CoroutineScope()` uses [Dispatchers.Default] for its coroutines. + * * `MainScope()` uses [Dispatchers.Main] for its coroutines. + * + * **The key part of custom usage of `CustomScope` is cancelling it and the end of the lifecycle.** + * The [CoroutineScope.cancel] extension function shall be used when the entity that was launching coroutines + * is no longer needed. It cancels all the coroutines that might still be running on behalf of it. + * + * For example: * * ``` * class MyUIClass { - * val scope = MainScope() // the scope of MyUIClass + * val scope = MainScope() // the scope of MyUIClass, uses Dispatchers.Main * * fun destroy() { // destroys an instance of MyUIClass * scope.cancel() // cancels all coroutines launched in this scope @@ -124,25 +138,81 @@ public val CoroutineScope.isActive: Boolean /** * 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. + * Active coroutines launched in `GlobalScope` do not keep the process alive. They are like daemon threads. + * + * This is a **delicate** API. It is easy to accidentally create resource or memory leaks when + * `GlobalScope` is used. A coroutine launched in `GlobalScope` is not subject to the principle of structured + * concurrency, so if it hangs or gets delayed due to a problem (e.g. due to a slow network), it will stay working + * and consuming resources. For example, consider the following code: + * + * ``` + * fun loadConfiguration() { + * GlobalScope.launch { + * val config = fetchConfigFromServer() // network request + * updateConfiguration(config) + * } + * } + * ``` + * + * A call to `loadConfiguration` creates a coroutine in the `GlobalScope` that works in background without any + * provision to cancel it or to wait for its completion. If a network is slow, it keeps waiting in background, + * consuming resources. Repeated calls to `loadConfiguration` will consume more and more resources. + * + * ### Possible replacements + * + * In many cases uses of `GlobalScope` should be removed, marking the containing operation with `suspend`, for example: + * + * ``` + * suspend fun loadConfiguration() { + * val config = fetchConfigFromServer() // network request + * updateConfiguration(config) + * } + * ``` + * + * In cases when `GlobalScope.launch` was used to launch multiple concurrent operations, the corresponding + * operations shall be grouped with [coroutineScope] instead: + * + * ``` + * // concurrently load configuration and data + * suspend fun loadConfigurationAndData() { + * coroutinesScope { + * launch { loadConfiguration() } + * launch { loadData() } + * } + * } + * ``` + * + * In top-level code, when launching a concurrent operation operation from a non-suspending context, an appropriately + * confined instance of [CoroutineScope] shall be used instead of a `GlobalScope`. See docs on [CoroutineScope] for + * details. + * + * ### GlobalScope vs custom scope + * + * Do not replace `GlobalScope.launch { ... }` with `CoroutineScope().launch { ... }` constructor function call. + * The latter has the same pitfalls as `GlobalScope`. See [CoroutineScope] documentation on the intended usage of + * `CoroutineScope()` constructor function. + * + * ### Legitimate use-cases * - * Usage of this interface may look like this: + * There are limited circumstances under which `GlobalScope` can be legitimately and safely used, such as top-level background + * processes that must stay active for the whole duration of the application's lifetime. Because of that, any use + * of `GlobalScope` requires an explicit opt-in with `@OptIn(DelicateCoroutinesApi::class)`, like this: * * ``` - * fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) { - * for (number in this) { - * send(Math.sqrt(number)) + * // A global coroutine to log statistics every second, must be always active + * @OptIn(DelicateCoroutinesApi::class) + * val globalScopeReporter = GlobalScope.launch { + * while (true) { + * delay(1000) + * logStatistics() * } * } * ``` */ +@DelicateCoroutinesApi public object GlobalScope : CoroutineScope { /** * Returns [EmptyCoroutineContext]. |