@@ -11,11 +11,11 @@ import kotlin.coroutines.*
11
11
* to describe when and how the coroutine should be dispatched initially.
12
12
*
13
13
* This parameter only affects how the coroutine behaves until the code of its body starts executing.
14
- * After that, cancellability and dispatching depend on the implementation details of the invoked suspending functions.
14
+ * After that, cancellability and dispatching are defined by the behavior of the invoked suspending functions.
15
15
*
16
16
* The summary of coroutine start options is:
17
17
* - [DEFAULT] immediately schedules the coroutine for execution according to its context.
18
- * - [LAZY] delays the moment of the initial dispatch until the result of the coroutine is awaited .
18
+ * - [LAZY] delays the moment of the initial dispatch until the result of the coroutine is needed .
19
19
* - [ATOMIC] prevents the coroutine from being cancelled before it starts, ensuring that its code will start
20
20
* executing in any case.
21
21
* - [UNDISPATCHED] immediately executes the coroutine until its first suspension point _in the current thread_.
@@ -46,7 +46,7 @@ public enum class CoroutineStart {
46
46
* // Dispatch the job to execute later.
47
47
* // The parent coroutine's dispatcher is inherited by default.
48
48
* // In this case, it's the single thread backing `runBlocking`.
49
- * val job = launch { // CoroutineStart.DEFAULT is the launch's default start mode
49
+ * launch { // CoroutineStart.DEFAULT is launch's default start mode
50
50
* println("3. When the thread is available, we start the coroutine")
51
51
* }
52
52
* println("2. The thread keeps doing other work after launching the coroutine")
@@ -59,7 +59,7 @@ public enum class CoroutineStart {
59
59
* println("1. About to start a coroutine not needing a dispatch.")
60
60
* // Dispatch the job to execute.
61
61
* // `Dispatchers.Unconfined` is explicitly chosen.
62
- * val job = launch(Dispatchers.Unconfined) { // CoroutineStart.DEFAULT is the launch's default start mode
62
+ * launch(Dispatchers.Unconfined) { // CoroutineStart.DEFAULT is the launch's default start mode
63
63
* println("2. The body will be executed immediately")
64
64
* delay(50.milliseconds) // give up the thread to the outer coroutine
65
65
* println("4. When the thread is next available, this coroutine proceeds further")
@@ -109,7 +109,7 @@ public enum class CoroutineStart {
109
109
* }
110
110
* ```
111
111
*
112
- * Behavior of [LAZY] can be described with the following examples:
112
+ * The behavior of [LAZY] can be described with the following examples:
113
113
*
114
114
* ```
115
115
* // Example of lazily starting a new coroutine that goes through a dispatch
@@ -160,52 +160,44 @@ public enum class CoroutineStart {
160
160
*
161
161
* Example:
162
162
* ```
163
- * val N_PERMITS = 3
164
- * val semaphore = Semaphore(N_PERMITS)
165
- * try {
166
- * repeat(100) {
167
- * semaphore.acquire()
168
- * if (it != 7) {
169
- * println("Scheduling $it...")
170
- * } else {
171
- * // "randomly" cancel the whole procedure
172
- * cancel()
173
- * }
174
- * launch(Dispatchers.Default, start = CoroutineStart.ATOMIC) {
175
- * println("Entered $it")
176
- * try {
177
- * // this `try` block will be entered in any case because of ATOMIC
178
- * println("Performing the procedure $it")
179
- * delay(10.milliseconds) // may throw due to cancellation
180
- * println("Done with the procedure $it")
181
- * } finally {
182
- * semaphore.release()
183
- * }
184
- * }
185
- * }
186
- * } finally {
187
- * withContext(NonCancellable) {
188
- * repeat(N_PERMITS) { semaphore.acquire() }
189
- * println("All permits were successfully returned!")
163
+ * val mutex = Mutex()
164
+ *
165
+ * mutex.lock() // lock the mutex outside the coroutine
166
+ * delay(10.milliseconds) // initial portion of the work, protected by the mutex
167
+ * val job = launch(start = CoroutineStart.ATOMIC) {
168
+ * // the work must continue in a coroutine, but still under the mutex
169
+ * println("Coroutine running!")
170
+ * try {
171
+ * // this `try` block will be entered in any case because of ATOMIC
172
+ * println("Starting task...")
173
+ * delay(10.milliseconds) // throws due to cancellation
174
+ * println("Finished task.")
175
+ * } finally {
176
+ * mutex.unlock() // correctly release the mutex
190
177
* }
191
178
* }
179
+ *
180
+ * job.cancelAndJoin() // we immediately cancel the coroutine.
181
+ * mutex.withLock {
182
+ * println("The lock has been returned correctly!")
183
+ * }
192
184
* ```
193
185
*
194
186
* Here, we used [ATOMIC] to ensure that a semaphore that was acquired outside of the coroutine does get released
195
187
* even if cancellation happens between `acquire()` and `launch`.
196
188
* As a result, the semaphore will eventually regain all three permits.
197
189
*
198
- * Behavior of [ATOMIC] can be described with the following examples:
190
+ * The behavior of [ATOMIC] can be described with the following examples:
199
191
*
200
192
* ```
201
193
* // Example of cancelling atomically started coroutines
202
194
* runBlocking {
203
195
* println("1. Atomically starting a coroutine that goes through a dispatch.")
204
196
* launch(start = CoroutineStart.ATOMIC) {
205
- * check(!isActive) // attempting to suspend will throw
206
- * println("4. A coroutine that went through a dispatch also starts.")
197
+ * check(!isActive) // attempting to suspend later will throw
198
+ * println("4. The coroutine was cancelled (isActive = $isActive), but starts anyway .")
207
199
* try {
208
- * delay(10.milliseconds)
200
+ * delay(10.milliseconds) // will throw: the coroutine is cancelled
209
201
* println("This code will never run.")
210
202
* } catch (e: CancellationException) {
211
203
* println("5. Cancellation at later points still works.")
@@ -243,48 +235,32 @@ public enum class CoroutineStart {
243
235
*
244
236
* Example:
245
237
* ```
246
- * runBlocking {
247
- * val channel = Channel<Int>(Channel.RENDEZVOUS)
248
- * var subscribers = 0
249
- * fun CoroutineScope.awaitTickNumber(desiredTickNumber: Int) {
250
- * launch(start = CoroutineStart.UNDISPATCHED) {
251
- * ++subscribers
252
- * try {
253
- * for (tickNumber in channel) {
254
- * if (tickNumber >= desiredTickNumber) {
255
- * println("Tick number $desiredTickNumber reached")
256
- * break
257
- * }
258
- * }
259
- * } finally {
260
- * --subscribers
261
- * }
238
+ * var tasks = 0
239
+ * repeat(3) {
240
+ * launch(start = CoroutineStart.UNDISPATCHED) {
241
+ * tasks++
242
+ * try {
243
+ * println("Waiting for a reply...")
244
+ * delay(50.milliseconds)
245
+ * println("Got a reply!")
246
+ * } finally {
247
+ * tasks--
262
248
* }
263
249
* }
264
- * for (subscriberIndex in 1..10) {
265
- * awaitTickNumber(10 + subscriberIndex * 3)
266
- * }
267
- * // Send the current tick number every 10 milliseconds
268
- * // while there are subscribers
269
- * var i = 0
270
- * // Because of UNDISPATCHED,
271
- * // we know that the subscribers are already initialized,
272
- * // so this number is non-zero initially.
273
- * while (subscribers > 0) {
274
- * channel.trySend(++i)
275
- * delay(10.milliseconds)
276
- * }
250
+ * }
251
+ * // Because of UNDISPATCHED,
252
+ * // we know that the tasks already ran to their first suspension point,
253
+ * // so this number is non-zero initially.
254
+ * while (tasks > 0) {
255
+ * println("currently active: $tasks")
256
+ * delay(10.milliseconds)
277
257
* }
278
258
* ```
279
259
*
280
260
* Here, we implement a publisher-subscriber interaction, where [UNDISPATCHED] ensures that the
281
261
* subscribers do get registered before the publisher first checks if it can stop emitting values due to
282
262
* the lack of subscribers.
283
263
*
284
- * **Pitfall**: unlike [Dispatchers.Unconfined] and [MainCoroutineDispatcher.immediate], nested undispatched
285
- * coroutines do not form an event loop that otherwise prevents potential stack overflow in case of unlimited
286
- * nesting.
287
- *
288
264
* ```
289
265
* // Constant usage of stack space
290
266
* fun CoroutineScope.factorialWithUnconfined(n: Int): Deferred<Int> =
@@ -311,12 +287,12 @@ public enum class CoroutineStart {
311
287
* whereas `factorialWithUndispatched` will lead to `n` recursively nested calls,
312
288
* resulting in a stack overflow for large values of `n`.
313
289
*
314
- * Behavior of [UNDISPATCHED] can be described with the following examples:
290
+ * The behavior of [UNDISPATCHED] can be described with the following examples:
315
291
*
316
292
* ```
317
293
* runBlocking {
318
294
* println("1. About to start a new coroutine.")
319
- * val job = launch(Dispatchers.Default, start = CoroutineStart.UNDISPATCHED) {
295
+ * launch(Dispatchers.Default, start = CoroutineStart.UNDISPATCHED) {
320
296
* println("2. The coroutine is immediately started in the same thread.")
321
297
* delay(10.milliseconds)
322
298
* println("4. The execution continues in a Dispatchers.Default thread.")
@@ -338,6 +314,11 @@ public enum class CoroutineStart {
338
314
* println("4. Execution of the outer coroutine only continues later.")
339
315
* }
340
316
* ```
317
+ *
318
+ * **Pitfall**: unlike [Dispatchers.Unconfined] and [MainCoroutineDispatcher.immediate], nested undispatched
319
+ * coroutines do not form an event loop that otherwise prevents potential stack overflow in case of unlimited
320
+ * nesting.
321
+ * See [Dispatchers.Unconfined] for an explanation of event loops.
341
322
*/
342
323
UNDISPATCHED ;
343
324
0 commit comments