@@ -4,23 +4,143 @@ import kotlinx.coroutines.internal.*
4
4
import kotlin.coroutines.intrinsics.*
5
5
6
6
/* *
7
- * Yields the thread (or thread pool) of the current coroutine dispatcher
8
- * to other coroutines on the same dispatcher to run if possible.
7
+ * Suspends this coroutine and immediately schedules it for further execution.
9
8
*
9
+ * A coroutine run uninterrupted on a thread until the coroutine *suspend*,
10
+ * giving other coroutines a chance to use that thread for their own computations.
11
+ * Normally, coroutines suspend whenever they wait for something to happen:
12
+ * for example, trying to receive a value from a channel that's currently empty will suspend.
13
+ * Sometimes, a coroutine does not need to wait for anything,
14
+ * but we still want it to give other coroutines a chance to run.
15
+ * Calling [yield] has this effect:
16
+ *
17
+ * ```
18
+ * fun updateProgressBar(value: Int, marker: String) {
19
+ * print(marker)
20
+ * }
21
+ * val singleThreadedDispatcher = Dispatchers.Default.limitedParallelism(1)
22
+ * withContext(singleThreadedDispatcher) {
23
+ * launch {
24
+ * repeat(5) {
25
+ * updateProgressBar(it, "A")
26
+ * yield()
27
+ * }
28
+ * }
29
+ * launch {
30
+ * repeat(5) {
31
+ * updateProgressBar(it, "B")
32
+ * yield()
33
+ * }
34
+ * }
35
+ * }
36
+ * ```
37
+ *
38
+ * In this example, without the [yield], first, `A` would run its five stages of work to completion, and only then
39
+ * would `B` even start executing.
40
+ * With both `yield` calls, the coroutines share the single thread with each other after each stage of work.
41
+ * This is useful when several coroutines running on the same thread (or thread pool) must regularly publish
42
+ * their results for the program to stay responsive.
43
+ *
10
44
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while
11
45
* [yield] is invoked or while waiting for dispatch, it immediately resumes with [CancellationException].
12
46
* There is a **prompt cancellation guarantee**: even if this function is ready to return the result, but was cancelled
13
47
* while suspended, [CancellationException] will be thrown. See [suspendCancellableCoroutine] for low-level details.
14
48
*
15
- * **Note**: This function always [checks for cancellation][ensureActive] even when it does not suspend.
49
+ * **Note**: if there is only a single coroutine executing on the current dispatcher,
50
+ * it is possible that [yield] will not actually suspend.
51
+ * However, even in that case, the [check for cancellation][ensureActive] still happens.
52
+ *
53
+ * **Note**: if there is no [CoroutineDispatcher] in the context, it does not suspend.
54
+ *
55
+ * ## Pitfall: using `yield` to wait for something to happen
56
+ *
57
+ * Using `yield` for anything except a way to ensure responsiveness is often a problem.
58
+ * When possible, it is recommended to structure the code in terms of coroutines waiting for some events instead of
59
+ * yielding.
60
+ * Below, we list the common problems involving [yield] and outline how to avoid them.
61
+ *
62
+ * ### Case 1: using `yield` to ensure a specific interleaving of actions
63
+ *
64
+ * ```
65
+ * val singleThreadedDispatcher = Dispatchers.Default.limitedParallelism(1)
66
+ * withContext(singleThreadedDispatcher) {
67
+ * var value: Int? = null
68
+ * val job = launch { // a new coroutine on the same dispatcher
69
+ * // yield() // uncomment to see the crash
70
+ * value = 42
71
+ * println("2. Value provided")
72
+ * }
73
+ * check(value == null)
74
+ * println("No value yet!")
75
+ * println("1. Awaiting the value...")
76
+ * // ANTIPATTERN! DO NOT WRITE SUCH CODE!
77
+ * yield() // allow the other coroutine to run
78
+ * // job.join() // would work more reliably in this scenario!
79
+ * check(value != null)
80
+ * println("3. Obtained $value")
81
+ * }
82
+ * ```
83
+ *
84
+ * Here, [yield] allows `singleThreadedDispatcher` to execute the task that ultimately provides the `value`.
85
+ * Without the [yield], the `value != null` check would be executed directly after `Awaiting the value` is printed.
86
+ * However, if the value-producing coroutine is modified to suspend before providing the value, this will
87
+ * no longer work; explicitly waiting for the coroutine to finish via [Job.join] instead is robust against such changes.
88
+ *
89
+ * Therefore, it is an antipattern to use `yield` to synchronize code across several coroutines.
90
+ *
91
+ * ### Case 2: using `yield` in a loop to wait for something to happen
92
+ *
93
+ * ```
94
+ * val singleThreadedDispatcher = Dispatchers.Default.limitedParallelism(1)
95
+ * withContext(singleThreadedDispatcher) {
96
+ * var value: Int? = null
97
+ * val job = launch { // a new coroutine on the same dispatcher
98
+ * delay(1.seconds)
99
+ * value = 42
100
+ * }
101
+ * // ANTIPATTERN! DO NOT WRITE SUCH CODE!
102
+ * while (value == null) {
103
+ * yield() // allow the other coroutines to run
104
+ * }
105
+ * println("Obtained $value")
106
+ * }
107
+ * ```
108
+ *
109
+ * This example will lead to correct results no matter how much the value-producing coroutine suspends,
110
+ * but it is still flawed.
111
+ * For the one second that it takes for the other coroutine to obtain the value,
112
+ * `value == null` would be constantly re-checked, leading to unjustified resource consumption.
113
+ *
114
+ * In this specific case, [CompletableDeferred] can be used instead:
115
+ *
116
+ * ```
117
+ * val singleThreadedDispatcher = Dispatchers.Default.limitedParallelism(1)
118
+ * withContext(singleThreadedDispatcher) {
119
+ * val deferred = CompletableDeferred<Int>()
120
+ * val job = launch { // a new coroutine on the same dispatcher
121
+ * delay(1.seconds)
122
+ * deferred.complete(42)
123
+ * }
124
+ * val value = deferred.await()
125
+ * println("Obtained $value")
126
+ * }
127
+ * ```
128
+ *
129
+ * `while (channel.isEmpty) { yield() }; channel.receive()` can be replaced with just `channel.receive()`;
130
+ * `while (job.isActive) { yield() }` can be replaced with [`job.join()`][Job.join];
131
+ * in both cases, this will avoid the unnecessary work of checking the loop conditions.
132
+ * In general, seek ways to allow a coroutine to stay suspended until it actually has useful work to do.
133
+ *
134
+ * ## Implementation details
16
135
*
17
- * ### Implementation details
136
+ * Some coroutine dispatchers include optimizations that make yielding different from normal suspensions.
137
+ * For example, when yielding, [Dispatchers.Unconfined] checks whether there are any other coroutines in the event
138
+ * loop where the current coroutine executes; if not, the sole coroutine continues to execute without suspending.
139
+ * Also, `Dispatchers.IO` and `Dispatchers.Default` on the JVM tweak the scheduling behavior to improve liveness
140
+ * when `yield()` is used in a loop.
18
141
*
19
- * If the coroutine dispatcher is [Unconfined][Dispatchers.Unconfined], this
20
- * functions suspends only when there are other unconfined coroutines working and forming an event-loop.
21
- * For other dispatchers, this function calls [CoroutineDispatcher.dispatch] and
22
- * always suspends to be resumed later regardless of the result of [CoroutineDispatcher.isDispatchNeeded].
23
- * If there is no [CoroutineDispatcher] in the context, it does not suspend.
142
+ * For custom implementations of [CoroutineDispatcher], this function checks [CoroutineDispatcher.isDispatchNeeded] and
143
+ * then invokes [CoroutineDispatcher.dispatch] regardless of the result; no way is provided to change this behavior.
24
144
*/
25
145
public suspend fun yield (): Unit = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
26
146
val context = uCont.context
0 commit comments