• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<lambda>null1 package kotlinx.coroutines
2 
3 import kotlinx.coroutines.internal.*
4 import kotlin.coroutines.intrinsics.*
5 
6 /**
7  * Suspends this coroutine and immediately schedules it for further execution.
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  *
44  * This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while
45  * [yield] is invoked or while waiting for dispatch, it immediately resumes with [CancellationException].
46  * There is a **prompt cancellation guarantee**: even if this function is ready to return the result, but was cancelled
47  * while suspended, [CancellationException] will be thrown. See [suspendCancellableCoroutine] for low-level details.
48  *
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
135  *
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.
141  *
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.
144  */
145 public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
146     val context = uCont.context
147     context.ensureActive()
148     val cont = uCont.intercepted() as? DispatchedContinuation<Unit> ?: return@sc Unit
149     if (cont.dispatcher.safeIsDispatchNeeded(context)) {
150         // this is a regular dispatcher -- do simple dispatchYield
151         cont.dispatchYield(context, Unit)
152     } else {
153         // This is either an "immediate" dispatcher or the Unconfined dispatcher
154         // This code detects the Unconfined dispatcher even if it was wrapped into another dispatcher
155         val yieldContext = YieldContext()
156         cont.dispatchYield(context + yieldContext, Unit)
157         // Special case for the unconfined dispatcher that can yield only in existing unconfined loop
158         if (yieldContext.dispatcherWasUnconfined) {
159             // Means that the Unconfined dispatcher got the call, but did not do anything.
160             // See also code of "Unconfined.dispatch" function.
161             return@sc if (cont.yieldUndispatched()) COROUTINE_SUSPENDED else Unit
162         }
163         // Otherwise, it was some other dispatcher that successfully dispatched the coroutine
164     }
165     COROUTINE_SUSPENDED
166 }
167