package kotlinx.coroutines import kotlinx.coroutines.intrinsics.* import kotlin.coroutines.* /** * Defines start options for coroutines builders. * * It is used in the `start` parameter of coroutine builder functions like * [launch][CoroutineScope.launch] and [async][CoroutineScope.async] * to describe when and how the coroutine should be dispatched initially. * * This parameter only affects how the coroutine behaves until the code of its body starts executing. * After that, cancellability and dispatching are defined by the behavior of the invoked suspending functions. * * The summary of coroutine start options is: * - [DEFAULT] immediately schedules the coroutine for execution according to its context. * - [LAZY] delays the moment of the initial dispatch until the result of the coroutine is needed. * - [ATOMIC] prevents the coroutine from being cancelled before it starts, ensuring that its code will start * executing in any case. * - [UNDISPATCHED] immediately executes the coroutine until its first suspension point _in the current thread_. */ public enum class CoroutineStart { /** * Immediately schedules the coroutine for execution according to its context. This is usually the default option. * * [DEFAULT] uses the default dispatch procedure described in the [CoroutineDispatcher] documentation. * * If the coroutine's [Job] is cancelled before it started executing, then it will not start its * execution at all and will be considered [cancelled][Job.isCancelled]. * * Examples: * * ``` * // Example of starting a new coroutine that goes through a dispatch * runBlocking { * println("1. About to start a new coroutine.") * // Dispatch the job to execute later. * // The parent coroutine's dispatcher is inherited by default. * // In this case, it's the single thread backing `runBlocking`. * launch { // CoroutineStart.DEFAULT is launch's default start mode * println("3. When the thread is available, we start the coroutine") * } * println("2. The thread keeps doing other work after launching the coroutine") * } * ``` * * ``` * // Example of starting a new coroutine that doesn't go through a dispatch initially * runBlocking { * println("1. About to start a coroutine not needing a dispatch.") * // Dispatch the job to execute. * // `Dispatchers.Unconfined` is explicitly chosen. * launch(Dispatchers.Unconfined) { // CoroutineStart.DEFAULT is the launch's default start mode * println("2. The body will be executed immediately") * delay(50.milliseconds) // give up the thread to the outer coroutine * println("4. When the thread is next available, this coroutine proceeds further") * } * println("3. After the initial suspension, the thread does other work.") * } * ``` * * ``` * // Example of cancelling coroutines before they start executing. * runBlocking { * // dispatch the job to execute on this thread later * launch { // CoroutineStart.DEFAULT is the launch's default start mode * println("This code will never execute") * } * cancel() // cancels the current coroutine scope and its children * launch(Dispatchers.Unconfined) { * println("This code will never execute") * } * println("This code will execute.") * } * ``` */ DEFAULT, /** * Starts the coroutine lazily, only when it is needed. * * Starting a coroutine with [LAZY] only creates the coroutine, but does not schedule it for execution. * When the completion of the coroutine is first awaited * (for example, via [Job.join]) or explicitly [started][Job.start], * the dispatch procedure described in the [CoroutineDispatcher] documentation is performed in the thread * that did it. * * The details of what counts as waiting can be found in the documentation of the corresponding coroutine builders * like [launch][CoroutineScope.launch] and [async][CoroutineScope.async]. * * If the coroutine's [Job] is cancelled before it started executing, then it will not start its * execution at all and will be considered [cancelled][Job.isCancelled]. * * **Pitfall**: launching a coroutine with [LAZY] without awaiting or cancelling it at any point means that it will * never be completed, leading to deadlocks and resource leaks. * For example, the following code will deadlock, since [coroutineScope] waits for all of its child coroutines to * complete: * ``` * // This code hangs! * coroutineScope { * launch(start = CoroutineStart.LAZY) { } * } * ``` * * The behavior of [LAZY] can be described with the following examples: * * ``` * // Example of lazily starting a new coroutine that goes through a dispatch * runBlocking { * println("1. About to start a new coroutine.") * // Create a job to execute on `Dispatchers.Default` later. * val job = launch(Dispatchers.Default, start = CoroutineStart.LAZY) { * println("3. Only now does the coroutine start.") * } * delay(10.milliseconds) // try to give the coroutine some time to run * println("2. The coroutine still has not started. Now, we join it.") * job.join() * } * ``` * * ``` * // Example of lazily starting a new coroutine that doesn't go through a dispatch initially * runBlocking { * println("1. About to lazily start a new coroutine.") * // Create a job to execute on `Dispatchers.Unconfined` later. * val lazyJob = launch(Dispatchers.Unconfined, start = CoroutineStart.LAZY) { * println("3. The coroutine starts on the thread that called `join`.") * } * // We start the job on another thread for illustrative purposes * launch(Dispatchers.Default) { * println("2. We start the lazyJob.") * job.start() // runs lazyJob's code in-place * println("4. Only now does the `start` call return.") * } * } * ``` * * ## Alternatives * * The effects of [LAZY] can usually be achieved more idiomatically without it. * * When a coroutine is started with [LAZY] and is stored in a property, * it may be a better choice to use [lazy] instead: * * ``` * // instead of `val page = scope.async(start = CoroutineStart.LAZY) { getPage() }`, do * val page by lazy { scope.async { getPage() } } * ``` * * This way, the child coroutine is not created at all unless it is needed. * Note that with this, any access to this variable will start the coroutine, * even something like `page.invokeOnCompletion { }` or `page.isActive`. * * If a coroutine is started with [LAZY] and then unconditionally started, * it is more idiomatic to create the coroutine in the exact place where it is started: * * ``` * // instead of `val job = scope.launch(start = CoroutineStart.LAZY) { }; job.start()`, do * scope.launch { } * ``` */ LAZY, /** * Atomically (i.e., in a non-cancellable way) schedules the coroutine for execution according to its context. * * This is similar to [DEFAULT], but the coroutine is guaranteed to start executing even if it was cancelled. * This only affects the behavior until the body of the coroutine starts executing; * inside the body, cancellation will work as usual. * * Like [ATOMIC], [UNDISPATCHED], too, ensures that coroutines will be started in any case. * The difference is that, instead of immediately starting them on the same thread, * [ATOMIC] performs the full dispatch procedure just as [DEFAULT] does. * * Because of this, we can use [ATOMIC] in cases where we want to be certain that some code eventually runs * and uses a specific dispatcher to do that. * * Example: * ``` * val mutex = Mutex() * * mutex.lock() // lock the mutex outside the coroutine * // ... // initial portion of the work, protected by the mutex * val job = launch(start = CoroutineStart.ATOMIC) { * // the work must continue in a coroutine, but still under the mutex * println("Coroutine running!") * try { * // this `try` block will be entered in any case because of ATOMIC * println("Starting task...") * delay(10.milliseconds) // throws due to cancellation * println("Finished task.") * } finally { * mutex.unlock() // correctly release the mutex * } * } * * job.cancelAndJoin() // we immediately cancel the coroutine. * mutex.withLock { * println("The lock has been returned correctly!") * } * ``` * * Here, we used [ATOMIC] to ensure that a mutex that was acquired outside the coroutine does get released * even if cancellation happens between `lock()` and `launch`. * As a result, the mutex will always be released. * * The behavior of [ATOMIC] can be described with the following examples: * * ``` * // Example of cancelling atomically started coroutines * runBlocking { * println("1. Atomically starting a coroutine that goes through a dispatch.") * launch(start = CoroutineStart.ATOMIC) { * check(!isActive) // attempting to suspend later will throw * println("4. The coroutine was cancelled (isActive = $isActive), but starts anyway.") * try { * delay(10.milliseconds) // will throw: the coroutine is cancelled * println("This code will never run.") * } catch (e: CancellationException) { * println("5. Cancellation at later points still works.") * throw e * } * } * println("2. Cancelling this coroutine and all of its children.") * cancel() * launch(Dispatchers.Unconfined, start = CoroutineStart.ATOMIC) { * check(!isActive) // attempting to suspend will throw * println("3. An undispatched coroutine starts.") * } * ensureActive() // we can even crash the current coroutine. * } * ``` * * This is a **delicate** API. The coroutine starts execution even if its [Job] is cancelled before starting. * However, the resources used within a coroutine may rely on the cancellation mechanism, * and cannot be used after the [Job] cancellation. For instance, in Android development, updating a UI element * is not allowed if the coroutine's scope, which is tied to the element's lifecycle, has been cancelled. */ @DelicateCoroutinesApi ATOMIC, /** * Immediately executes the coroutine until its first suspension point _in the current thread_. * * Starting a coroutine using [UNDISPATCHED] is similar to using [Dispatchers.Unconfined] with [DEFAULT], except: * - Resumptions from later suspensions will properly use the actual dispatcher from the coroutine's context. * Only the code until the first suspension point will be executed immediately. * - Even if the coroutine was cancelled already, its code will still start running, similar to [ATOMIC]. * - The coroutine will not form an event loop. See [Dispatchers.Unconfined] for an explanation of event loops. * * This set of behaviors makes [UNDISPATCHED] well-suited for cases where the coroutine has a distinct * initialization phase whose side effects we want to rely on later. * * Example: * ``` * var tasks = 0 * repeat(3) { * launch(start = CoroutineStart.UNDISPATCHED) { * tasks++ * try { * println("Waiting for a reply...") * delay(50.milliseconds) * println("Got a reply!") * } finally { * tasks-- * } * } * } * // Because of UNDISPATCHED, * // we know that the tasks already ran to their first suspension point, * // so this number is non-zero initially. * while (tasks > 0) { * println("currently active: $tasks") * delay(10.milliseconds) * } * ``` * * Here, we implement a publisher-subscriber interaction, where [UNDISPATCHED] ensures that the * subscribers do get registered before the publisher first checks if it can stop emitting values due to * the lack of subscribers. * * ``` * // Constant usage of stack space * fun CoroutineScope.factorialWithUnconfined(n: Int): Deferred = * async(Dispatchers.Unconfined) { * if (n > 0) { * n * factorialWithUnconfined(n - 1).await() * } else { * 1 // replace with `error()` to see the stacktrace * } * } * * // Linearly increasing usage of stack space * fun CoroutineScope.factorialWithUndispatched(n: Int): Deferred = * async(start = CoroutineStart.UNDISPATCHED) { * if (n > 0) { * n * factorialWithUndispatched(n - 1).await() * } else { * 1 // replace with `error()` to see the stacktrace * } * } * ``` * * Calling `factorialWithUnconfined` from this example will result in a constant-size stack, * whereas `factorialWithUndispatched` will lead to `n` recursively nested calls, * resulting in a stack overflow for large values of `n`. * * The behavior of [UNDISPATCHED] can be described with the following examples: * * ``` * runBlocking { * println("1. About to start a new coroutine.") * launch(Dispatchers.Default, start = CoroutineStart.UNDISPATCHED) { * println("2. The coroutine is immediately started in the same thread.") * delay(10.milliseconds) * println("4. The execution continues in a Dispatchers.Default thread.") * } * println("3. Execution of the outer coroutine only continues later.") * } * ``` * * ``` * // Cancellation does not prevent the coroutine from being started * runBlocking { * println("1. First, we cancel this scope.") * cancel() * println("2. Now, we start a new UNDISPATCHED child.") * launch(start = CoroutineStart.UNDISPATCHED) { * check(!isActive) // the child is already cancelled * println("3. We entered the coroutine despite being cancelled.") * } * println("4. Execution of the outer coroutine only continues later.") * } * ``` * * **Pitfall**: unlike [Dispatchers.Unconfined] and [MainCoroutineDispatcher.immediate], nested undispatched * coroutines do not form an event loop that otherwise prevents potential stack overflow in case of unlimited * nesting. This property is necessary for the use case of guaranteed initialization, but may be undesirable in * other cases. * See [Dispatchers.Unconfined] for an explanation of event loops. */ UNDISPATCHED; /** * Starts the corresponding block with receiver as a coroutine with this coroutine start strategy. * * - [DEFAULT] uses [startCoroutineCancellable]. * - [ATOMIC] uses [startCoroutine]. * - [UNDISPATCHED] uses [startCoroutineUndispatched]. * - [LAZY] does nothing. * * @suppress **This an internal API and should not be used from general code.** */ @InternalCoroutinesApi public operator fun invoke(block: suspend R.() -> T, receiver: R, completion: Continuation): Unit = when (this) { DEFAULT -> block.startCoroutineCancellable(receiver, completion) ATOMIC -> block.startCoroutine(receiver, completion) UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion) LAZY -> Unit // will start lazily } /** * Returns `true` when [LAZY]. * * @suppress **This an internal API and should not be used from general code.** */ @InternalCoroutinesApi public val isLazy: Boolean get() = this === LAZY }