@@ -17,25 +17,168 @@ import kotlin.jvm.*
1717// --------------- launch ---------------
1818
1919/* *
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].
2222 *
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.
2725 *
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+ * ```
33169 *
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:
37171 *
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+ * ```
39182 *
40183 * @param context additional to [CoroutineScope.coroutineContext] context of the coroutine.
41184 * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
0 commit comments