Skip to content

Commit

Permalink
Improve documentation
Browse files Browse the repository at this point in the history
    * Clarifications of CoroutineExceptionHandler execution
    * Clarifications of MainCoroutineDispatcher.immediate
    * Outdated documentation (pointed out by Google AndoidX team) for isDispatchNeeded is rewritten
    *

Fixes Kotlin#1650
Fixes Kotlin#1651
Fixes Kotlin#1634
  • Loading branch information
qwwdfsad committed Nov 28, 2019
1 parent ef199d1 commit 445e026
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 34 deletions.
3 changes: 2 additions & 1 deletion docs/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
46 changes: 15 additions & 31 deletions kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion kotlinx-coroutines-core/common/src/Dispatchers.common.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
* ```
Expand Down

0 comments on commit 445e026

Please sign in to comment.