@@ -17,25 +17,168 @@ import kotlin.jvm.*
17
17
// --------------- launch ---------------
18
18
19
19
/* *
20
- * Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a [Job].
21
- * The coroutine is cancelled when the resulting job is [cancelled][ Job.cancel ].
20
+ * Launches a new *child coroutine* of [CoroutineScope] without blocking the current thread
21
+ * and returns a reference to the coroutine as a [ Job].
22
22
*
23
- * The coroutine context is inherited from a [CoroutineScope]. Additional context elements can be specified with [context] argument.
24
- * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
25
- * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
26
- * with a corresponding [context] element.
23
+ * [block] is the computation of the new coroutine that will run concurrently.
24
+ * The coroutine is considered active until the block and all the child coroutines created in it finish.
27
25
*
28
- * By default, the coroutine is immediately scheduled for execution.
29
- * Other start options can be specified via `start` parameter. See [CoroutineStart] for details.
30
- * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,
31
- * the coroutine [Job] is created in _new_ state. It can be explicitly started with [start][Job.start] function
32
- * and will be started implicitly on the first invocation of [join][Job.join].
26
+ * [context] specifies the additional context elements for the coroutine to combine with
27
+ * the elements already present in the [CoroutineScope.coroutineContext].
28
+ * It is incorrect to pass a [Job] element there, as this breaks structured concurrency.
29
+ *
30
+ * By default, the coroutine is scheduled for execution on its [ContinuationInterceptor].
31
+ * There is no guarantee that it will start immediately: this is decided by the [ContinuationInterceptor].
32
+ * It is possible that the new coroutine will be cancelled before starting, in which case its code will not be executed.
33
+ * The [start] parameter can be used to adjust this behavior. See [CoroutineStart] for details.
34
+ *
35
+ * ## Structured Concurrency
36
+ *
37
+ * [launch] creates a *child coroutine* of `this` [CoroutineScope].
38
+ *
39
+ * The context of the new coroutine is created like this:
40
+ * - First, the context of the [CoroutineScope] is combined with the [context] argument
41
+ * using the [newCoroutineContext] function.
42
+ * In most cases, this means that elements from [context] simply override
43
+ * the elements in the [CoroutineScope.coroutineContext].
44
+ * If no [ContinuationInterceptor] is present in the resulting context,
45
+ * then [Dispatchers.Default] is added there.
46
+ * - Then, the [Job] in the [CoroutineScope.coroutineContext] is used as the *parent* of the new coroutine,
47
+ * unless overridden.
48
+ * Overriding the [Job] is forbidden; see a separate subsection below for details.
49
+ * The new coroutine's [Job] is added to the resulting context.
50
+ *
51
+ * The resulting coroutine context is the [coroutineContext] of the [CoroutineScope]
52
+ * passed to the [block] as its receiver.
53
+ *
54
+ * The new coroutine is considered [active][isActive] until the [block] and all its child coroutines finish.
55
+ * If the [block] throws a [CancellationException], the coroutine is considered cancelled,
56
+ * and if it throws any other exception, the coroutine is considered failed.
57
+ *
58
+ * The details of structured concurrency are described in the [CoroutineScope] interface documentation.
59
+ * Here is a restatement of some main points as they relate to `launch`:
60
+ *
61
+ * - The lifecycle of the parent [CoroutineScope] can not end until this coroutine
62
+ * (as well as all its children) completes.
63
+ * - If the parent [CoroutineScope] is cancelled, this coroutine is cancelled as well.
64
+ * - If this coroutine fails with a non-[CancellationException] exception
65
+ * and the parent [CoroutineScope] has a non-supervisor [Job] in its context,
66
+ * the parent [Job] is cancelled with this exception.
67
+ * - If this coroutine fails with an exception and the parent [CoroutineScope] has a supervisor [Job] or no job at all
68
+ * (as is the case with [GlobalScope] or malformed scopes),
69
+ * the exception is considered uncaught and is propagated as the [CoroutineExceptionHandler] documentation describes.
70
+ * - The lifecycle of the [CoroutineScope] passed as the receiver to the [block]
71
+ * will not end until the [block] completes (or gets cancelled before ever having a chance to run).
72
+ * - If the [block] throws a [CancellationException], the coroutine is considered cancelled,
73
+ * cancelling all its children in turn, but the parent does not get notified.
74
+ *
75
+ * ### Overriding the parent job
76
+ *
77
+ * Passing a [Job] in the [context] argument breaks structured concurrency and is not a supported pattern.
78
+ * It does not throw an exception only for backward compatibility reasons, as a lot of code was written this way.
79
+ * Always structure your coroutines such that the lifecycle of the child coroutine is
80
+ * contained in the lifecycle of the [CoroutineScope] it is launched in.
81
+ *
82
+ * To help with migrating to structured concurrency, the specific behaviour of passing a [Job] in the [context] argument
83
+ * is described here.
84
+ * **Do not rely on this behaviour in new code.**
85
+ *
86
+ * If [context] contains a [Job] element, it will be the *parent* of the new coroutine,
87
+ * and the lifecycle of the new coroutine will not be tied to the [CoroutineScope] at all.
88
+ *
89
+ * In specific terms:
90
+ *
91
+ * - If the [CoroutineScope] is cancelled, the new coroutine will not be affected.
92
+ * - If the new coroutine fails with an exception, it will not cancel the [CoroutineScope].
93
+ * Instead, the exception will be propagated to the [Job] passed in the [context] argument.
94
+ * If that [Job] is a [SupervisorJob], the exception will be unhandled,
95
+ * and will be propagated as the [CoroutineExceptionHandler] documentation describes.
96
+ * If that [Job] is not a [SupervisorJob], it will be cancelled with the exception thrown by [launch].
97
+ * - If the [CoroutineScope] is lexically scoped (for example, created by [coroutineScope] or [withContext]),
98
+ * the function defining the scope will not wait for the new coroutine to finish.
99
+ *
100
+ * ## Communicating with the coroutine
101
+ *
102
+ * [Job.cancel] can be used to cancel the coroutine, and [Job.join] can be used to block until its completion
103
+ * without blocking the current thread.
104
+ * Note that [Job.join] succeeds even if the coroutine was cancelled or failed with an exception.
105
+ * [Job.cancelAndJoin] is a convenience function that combines cancellation and joining.
106
+ *
107
+ * If the coroutine was started with [start] set to [CoroutineStart.LAZY], the coroutine will not be scheduled
108
+ * to run on its [ContinuationInterceptor] immediately.
109
+ * [Job.start] can be used to start the coroutine explicitly,
110
+ * and awaiting its completion using [Job.join] also causes the coroutine to start executing.
111
+ *
112
+ * A coroutine created with [launch] does not return a result, and if it fails with an exception,
113
+ * there is no reliable way to learn about that exception in general.
114
+ * [async] is a better choice if the result of the coroutine needs to be accessed from another coroutine.
115
+ *
116
+ * ## Pitfalls
117
+ *
118
+ * ### [CancellationException] silently stopping computations
119
+ *
120
+ * ```
121
+ * val deferred = GlobalScope.async {
122
+ * awaitCancellation()
123
+ * }
124
+ * deferred.cancel()
125
+ * coroutineScope {
126
+ * val job = launch {
127
+ * val result = deferred.await()
128
+ * println("Got $result")
129
+ * }
130
+ * job.join()
131
+ * println("Am I still not cancelled? $isActive")
132
+ * }
133
+ * ```
134
+ *
135
+ * will output
136
+ *
137
+ * ```
138
+ * Am I still not cancelled? true
139
+ * ```
140
+ *
141
+ * This may be surprising, because the `launch`ed coroutine failed with an exception,
142
+ * but the parent still was not cancelled.
143
+ *
144
+ * The reason for this is that any [CancellationException] thrown in the coroutine is treated as a signal to cancel
145
+ * the coroutine, but not the parent.
146
+ * In this scenario, this is unlikely to be the desired behaviour:
147
+ * this was a failure and not a cancellation and should be propagated to the parent.
148
+ *
149
+ * This is a legacy behavior that cannot be changed in a backward-compatible way.
150
+ * Use [ensureActive] and [isActive] to distinguish between cancellation and failure:
151
+ *
152
+ * ```
153
+ * launch {
154
+ * try {
155
+ * val result = deferred.await()
156
+ * } catch (e: CancellationException) {
157
+ * if (isActive) {
158
+ * // we were not cancelled, this is a failure
159
+ * println("`result` was cancelled")
160
+ * throw IllegalStateException("$result was cancelled", e)
161
+ * } else {
162
+ * println("I was cancelled")
163
+ * // throw again to finish the coroutine
164
+ * ensureActive()
165
+ * }
166
+ * }
167
+ * }
168
+ * ```
33
169
*
34
- * Uncaught exceptions in this coroutine cancel the parent job in the context by default
35
- * (unless [CoroutineExceptionHandler] is explicitly specified), which means that when `launch` is used with
36
- * the context of another coroutine, then any uncaught exception leads to the cancellation of the parent coroutine.
170
+ * In simpler scenarios, this form can be used:
37
171
*
38
- * See [newCoroutineContext] for a description of debugging facilities that are available for a newly created coroutine.
172
+ * ```
173
+ * launch {
174
+ * try {
175
+ * // operation that may throw its own CancellationException
176
+ * } catch (e: CancellationException) {
177
+ * ensureActive()
178
+ * throw IllegalStateException(e)
179
+ * }
180
+ * }
181
+ * ```
39
182
*
40
183
* @param context additional to [CoroutineScope.coroutineContext] context of the coroutine.
41
184
* @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
0 commit comments