aboutsummaryrefslogtreecommitdiffstats
path: root/kotlinx-coroutines-core/common/src/CoroutineScope.kt
diff options
context:
space:
mode:
Diffstat (limited to 'kotlinx-coroutines-core/common/src/CoroutineScope.kt')
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineScope.kt96
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].