From 5af4f91be68f507dcb9ff9a6d4ee0ec10076af3b Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 25 Nov 2019 18:56:24 +0300 Subject: [PATCH] Improve documentation * Clarifications of CoroutineExceptionHandler execution * Clarifications of MainCoroutineDispatcher.immediate * Outdated documentation (pointed out by Google AndoidX team) for isDispatchNeeded is rewritten * Fixes #1650 Fixes #1651 Fixes #1634 --- docs/basics.md | 3 +- .../common/src/CoroutineDispatcher.kt | 46 ++++++------------- .../common/src/CoroutineExceptionHandler.kt | 7 ++- .../common/src/Dispatchers.common.kt | 2 +- .../common/src/MainCoroutineDispatcher.kt | 1 + 5 files changed, 25 insertions(+), 34 deletions(-) diff --git a/docs/basics.md b/docs/basics.md index 13760a618e..6c3c0caa78 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -75,7 +75,8 @@ Here we are launching a new coroutine in the [GlobalScope], meaning that the lif coroutine is limited only by the lifetime of the whole application. You can achieve the same result replacing -`GlobalScope.launch { ... }` with `thread { ... }` and `delay(...)` with `Thread.sleep(...)`. Try it. +`GlobalScope.launch { ... }` with `thread { ... }` and `delay(...)` with `Thread.sleep(...)`. +Try it (don't forget to import `kotlin.concurrent.thread`). If you start by replacing `GlobalScope.launch` by `thread`, the compiler produces the following error: diff --git a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt index f08f8f782f..b45bab3bc9 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt @@ -27,46 +27,30 @@ import kotlin.coroutines.* * * This class ensures that debugging facilities in [newCoroutineContext] function work properly. */ -public abstract class CoroutineDispatcher : - AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { +public abstract class CoroutineDispatcher + : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { + /** - * Returns `true` if the execution shall be dispatched onto another thread. + * Returns `true` if the execution of the coroutine should be performed with [dispatch] method. * The default behavior for most dispatchers is to return `true`. * - * This method should never be used from general code, it is used only by `kotlinx.coroutines` - * internals and its contract with the rest of the API is an implementation detail. - * - * UI dispatchers _should not_ override `isDispatchNeeded`, but leave the default implementation that - * returns `true`. To understand the rationale beyond this recommendation, consider the following code: - * - * ```kotlin - * fun asyncUpdateUI() = async(Dispatchers.Main) { - * // do something here that updates something in UI - * } - * ``` - * - * When you invoke `asyncUpdateUI` in some background thread, it immediately continues to the next - * line, while the UI update happens asynchronously in the UI thread. However, if you invoke - * it in the UI thread itself, it will update the UI _synchronously_ if your `isDispatchNeeded` is - * overridden with a thread check. Checking if we are already in the UI thread seems more - * efficient (and it might indeed save a few CPU cycles), but this subtle and context-sensitive - * difference in behavior makes the resulting async code harder to debug. + * If this method returns `false`, the coroutine is resumed immediately in the current thread, + * potentially forming an event-loop to prevent stack overflows. + * The event loop is an advanced topic and its implications can be found in [Dispatchers.Unconfined] documentation. * - * Basically, the choice here is between the "JS-style" asynchronous approach (async actions - * are always postponed to be executed later in the event dispatch thread) and "C#-style" approach - * (async actions are executed in the invoker thread until the first suspension point). - * While the C# approach seems to be more efficient, it ends up with recommendations like - * "use `yield` if you need to ....". This is error-prone. The JS-style approach is more consistent - * and does not require programmers to think about whether they need to yield or not. + * A dispatcher can override this method to provide a performance optimization and avoid paying a cost of an unnecessary dispatch. + * E.g. [MainCoroutineDispatcher.immediate] checks whether we are already in the required UI thread in this method and avoids + * an additional dispatch when it is not required. But this method should not return `false` by default, because + * it may expose unexpected behaviour (e.g. with interleaved event-loops) and unexpected order of events. * - * However, coroutine builders like [launch][CoroutineScope.launch] and [async][CoroutineScope.async] accept an optional [CoroutineStart] - * parameter that allows one to optionally choose the C#-style [CoroutineStart.UNDISPATCHED] behavior - * whenever it is needed for efficiency. + * Coroutine builders like [launch][CoroutineScope.launch] and [async][CoroutineScope.async] accept an optional [CoroutineStart] + * parameter that allows one to optionally choose the [undispatched][CoroutineStart.UNDISPATCHED] behavior to start coroutine immediately, + * but to be resumed only in the provided dispatcher. * * This method should generally be exception-safe. An exception thrown from this method * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state. * - * **Note: This is an experimental api.** Execution semantics of coroutines may change in the future when this function returns `false`. + * **This is an experimental api.** Execution semantics of coroutines may change in the future when this function returns `false`. */ @ExperimentalCoroutinesApi public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true diff --git a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt index eca095f817..ee440b5310 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt @@ -65,7 +65,12 @@ public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineConte * * if there is a [Job] in the context, then [Job.cancel] is invoked; * * Otherwise, all instances of [CoroutineExceptionHandler] found via [ServiceLoader] * * and current thread's [Thread.uncaughtExceptionHandler] are invoked. - **/ + * + * [CoroutineExceptionHandler] can be invoked from an arbitrary dispatcher used by coroutines in the current job hierarchy. + * For example, if one has a `MainScope` and launches children of the scope in main and default dispatchers, then exception handler can + * be invoked either in main or in default dispatcher thread regardless of + * which particular dispatcher coroutine that has thrown an exception used. + */ public interface CoroutineExceptionHandler : CoroutineContext.Element { /** * Key for [CoroutineExceptionHandler] instance in the coroutine context. diff --git a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt index ffb325a8d8..5a957e7555 100644 --- a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt +++ b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt @@ -66,7 +66,7 @@ public expect object Dispatchers { * Can print both "1 2 3" and "1 3 2", this is an implementation detail that can be changed. * But it is guaranteed that "Done" will be printed only when both `withContext` calls are completed. * - * Note that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption, + * If you need your coroutine to be confined to a particular thread or a thread-pool after resumption, * but still want to execute it in the current call-frame until its first suspension, then you can use * an optional [CoroutineStart] parameter in coroutine builders like * [launch][CoroutineScope.launch] and [async][CoroutineScope.async] setting it to diff --git a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt index 2a20095aee..bead3c89a4 100644 --- a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt +++ b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt @@ -18,6 +18,7 @@ public abstract class MainCoroutineDispatcher : CoroutineDispatcher() { * * Immediate dispatcher is safe from stack overflows and in case of nested invocations forms event-loop similar to [Dispatchers.Unconfined]. * The event loop is an advanced topic and its implications can be found in [Dispatchers.Unconfined] documentation. + * The formed event-loop is shared with [Unconfined] and other immediate dispatchers, potentially overlapping tasks between them. * * Example of usage: * ```