Skip to content

Commit 2f6d7c9

Browse files
committed
A guide on coroutine contexts, Here context renamed to Unconfined
1 parent b3d55a5 commit 2f6d7c9

File tree

15 files changed

+477
-32
lines changed

15 files changed

+477
-32
lines changed

coroutines-guide.md

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ This is a short guide on core features of `kotlinx.coroutines` with a series of
2222
* [Sequential by default](#sequential-by-default)
2323
* [Concurrent using deferred value](#concurrent-using-deferred-value)
2424
* [Lazily deferred value](#lazily-deferred-value)
25+
* [Coroutine context and dispatchers](#coroutine-context-and-dispatchers)
26+
* [Dispatchers and threads](#Dispatchers-and-threads)
27+
* [Unconfined vs confined dispatcher](#unconfined-vs-confined-dispatcher)
28+
* [Debugging coroutines and threads](#debugging-coroutines-and-threads)
29+
* [Jumping between threads](#jumping-between-threads)
30+
* [Job in the context](#job-in-the-context)
31+
* [Children of a coroutine](#children-of-a-coroutine)
32+
* [Combining contexts](#combining-contexts)
33+
* [Naming coroutines for debugging](#naming-coroutines-for-debugging)
2534

2635
<!--- KNIT kotlinx-coroutines-core/src/test/kotlin/guide/.*\.kt -->
2736

@@ -555,5 +564,310 @@ So, we are back to two sequential execution, because we _first_ await for the `o
555564
for the second one. It is not the intended use-case for `lazyDefer`. It is designed as a replacement for
556565
the standard `lazy` function in cases when computation of the value involve suspending functions.
557566

567+
## Coroutine context and dispatchers
568+
569+
We've already seen `launch(CommonPool) {...}`, `defer(CommonPool) {...}`, `run(NonCancellable) {...}`, etc.
570+
In these code snippets `CommonPool` and `NonCancellable` are _coroutine contexts_.
571+
This section covers other available choices.
572+
573+
### Dispatchers and threads
574+
575+
Coroutine context includes a _coroutine dispatcher_ which determines what thread or threads
576+
the corresponding coroutine uses for its execution. Coroutine dispatcher can confine coroutine execution
577+
to a specific thread, dispatch it to a thread pool, or let it run unconfined. Try the following example:
578+
579+
```kotlin
580+
fun main(args: Array<String>) = runBlocking<Unit> {
581+
val jobs = arrayListOf<Job>()
582+
jobs += launch(Unconfined) { // not confined -- will work with main thread
583+
println(" 'Unconfined': I'm working in thread ${Thread.currentThread().name}")
584+
}
585+
jobs += launch(context) { // context of the parent, runBlocking coroutine
586+
println(" 'context': I'm working in thread ${Thread.currentThread().name}")
587+
}
588+
jobs += launch(CommonPool) { // will get dispatched to ForkJoinPool.commonPool (or equivalent)
589+
println(" 'CommonPool': I'm working in thread ${Thread.currentThread().name}")
590+
}
591+
jobs += launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
592+
println(" 'newSTC': I'm working in thread ${Thread.currentThread().name}")
593+
}
594+
jobs.forEach { it.join() }
595+
}
596+
```
597+
598+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-41.kt)
599+
600+
It produces the following output (maybe in different order):
601+
602+
```
603+
'Unconfined': I'm working in thread main
604+
'CommonPool': I'm working in thread ForkJoinPool.commonPool-worker-1
605+
'newSTC': I'm working in thread MyOwnThread
606+
'context': I'm working in thread main
607+
```
608+
609+
The difference between parent `context` and `Unconfied` context will be shown later.
610+
611+
### Unconfined vs confined dispatcher
612+
613+
The `Unconfined` coroutine dispatcher starts coroutine in the caller thread, but only until the
614+
first suspension point. After suspension it resumes in the thread that is fully determined by the
615+
suspending function that was invoked. Unconfined dispatcher is appropriate when coroutine does not
616+
consume CPU time nor updates any shared data (like UI) that is confined to a specific thread.
617+
618+
On the other side, `context` property that is available inside the block of any coroutine
619+
via `CoroutineScope` interface, is a reference to a context of this particular coroutine.
620+
This way, a parent context can be inherited. The default context of `runBlocking`, in particular,
621+
is confined to be invoker thread, so inheriting it has the effect of confining execution to
622+
this thread with a predictable FIFO scheduling.
623+
624+
```kotlin
625+
fun main(args: Array<String>) = runBlocking<Unit> {
626+
val jobs = arrayListOf<Job>()
627+
jobs += launch(Unconfined) { // not confined -- will work with main thread
628+
println(" 'Unconfined': I'm working in thread ${Thread.currentThread().name}")
629+
delay(1000)
630+
println(" 'Unconfined': After delay in thread ${Thread.currentThread().name}")
631+
}
632+
jobs += launch(context) { // context of the parent, runBlocking coroutine
633+
println(" 'context': I'm working in thread ${Thread.currentThread().name}")
634+
delay(1000)
635+
println(" 'context': After delay in thread ${Thread.currentThread().name}")
636+
}
637+
jobs.forEach { it.join() }
638+
}
639+
```
640+
641+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-42.kt)
642+
643+
Produces the output:
644+
645+
```
646+
'Unconfined': I'm working in thread main
647+
'context': I'm working in thread main
648+
'Unconfined': After delay in thread kotlinx.coroutines.ScheduledExecutor
649+
'context': After delay in thread main
650+
```
651+
652+
So, the coroutine the had inherited `context` of `runBlocking {...}` continues to execute in the `main` thread,
653+
while the unconfined one had resumed in the scheduler thread that `delay` function is using.
654+
655+
### Debugging coroutines and threads
656+
657+
Coroutines can suspend on one thread and resume on another thread with `Unconfined` dispatcher or
658+
with a multi-threaded dispatcher like `CommonPool`. Even with a single-threaded dispatcher it might be hard to
659+
figure out what coroutine was doing what, where, and when. The common approach to debugging applications with
660+
threads is to print the thread name in the log file on each log statement. This feature is universally supported
661+
by logging frameworks. When using coroutines, the thread name alone does not give much of a context, so
662+
`kotlinx.coroutines` includes debugging facilities to make it easier.
663+
664+
Run the following code with `-Dkotlinx.coroutines.debug` JVM option:
665+
666+
```kotlin
667+
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
668+
669+
fun main(args: Array<String>) = runBlocking<Unit> {
670+
val a = defer(context) {
671+
log("I'm computing a piece of the answer")
672+
6
673+
}
674+
val b = defer(context) {
675+
log("I'm computing another piece of the answer")
676+
7
677+
}
678+
log("The answer is ${a.await() * b.await()}")
679+
}
680+
```
681+
682+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-43.kt)
683+
684+
There are three coroutines. The main couroutine (#1) -- `runBlocking` one,
685+
and two coroutines computing deferred values `a` (#2) and `b` (#3).
686+
They are all executing in the context of `runBlocking` and are confined to the main thread.
687+
The output of this code is:
688+
689+
```
690+
[main @coroutine#2] I'm computing a piece of the answer
691+
[main @coroutine#3] I'm computing another piece of the answer
692+
[main @coroutine#1] The answer is 42
693+
```
694+
695+
The `log` function prints the name of the thread in square brackets and you can see, that it is the `main`
696+
thread, but the identifier of the currently executing coroutine is appended to it. This identifier
697+
is consecutively assigned to all created coroutines when debugging mode is turned on.
698+
699+
You can read more about debugging facilities in documentation for `newCoroutineContext` function.
700+
701+
### Jumping between threads
702+
703+
Run the following code with `-Dkotlinx.coroutines.debug` JVM option:
704+
705+
```kotlin
706+
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
707+
708+
fun main(args: Array<String>) {
709+
val ctx1 = newSingleThreadContext("Ctx1")
710+
val ctx2 = newSingleThreadContext("Ctx2")
711+
runBlocking(ctx1) {
712+
log("Started in ctx1")
713+
run(ctx2) {
714+
log("Working in ctx2")
715+
}
716+
log("Back to ctx1")
717+
}
718+
}
719+
```
720+
721+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-44.kt)
722+
723+
It demonstrates two new techniques. One is using `runBlocking` with an explicitly specified context, and
724+
the second one is using `run(context) {...}` to change a context of a coroutine while still staying in the
725+
same coroutine as you can see in the output below:
726+
727+
```
728+
[Ctx1 @coroutine#1] Started in ctx1
729+
[Ctx2 @coroutine#1] Working in ctx2
730+
[Ctx1 @coroutine#1] Back to ctx1
731+
```
732+
733+
### Job in the context
734+
735+
The coroutine `Job` is part of its context. The coroutine can retrieve it from its own context
736+
using `context[Job]` expression:
737+
738+
```kotlin
739+
fun main(args: Array<String>) = runBlocking<Unit> {
740+
println("My job is ${context[Job]}")
741+
}
742+
```
743+
744+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-45.kt)
745+
746+
It produces
747+
748+
```
749+
My job is BlockingCoroutine{isActive=true}
750+
```
751+
752+
So, `isActive` in `CoroutineScope` is just a convenient shortcut for `context[Job]!!.isActive`.
753+
754+
### Children of a coroutine
755+
756+
When `context` of a coroutine is used to launch another coroutine, the `Job` of the new coroutine becomes
757+
a _child_ of the parent coroutine's job. When the parent coroutine is cancelled, all its children
758+
are recursively cancelled, too.
759+
760+
```kotlin
761+
fun main(args: Array<String>) = runBlocking<Unit> {
762+
// start a coroutine to process some kind of incoming request
763+
val request = launch(CommonPool) {
764+
// it spawns two other jobs, one with its separate context
765+
val job1 = launch(CommonPool) {
766+
println("job1: I have my own context and execute independently!")
767+
delay(1000)
768+
println("job1: I am not affected by cancellation of the request")
769+
}
770+
// and the other inherits the parent context
771+
val job2 = launch(context) {
772+
println("job2: I am a child of the request coroutine")
773+
delay(1000)
774+
println("job2: I will not execute this line if my parent request is cancelled")
775+
}
776+
// request completes when both its sub-jobs complete:
777+
job1.join()
778+
job2.join()
779+
}
780+
delay(500)
781+
request.cancel() // cancel processing of the request
782+
delay(1000) // delay a second to see what happens
783+
println("main: Who has survived request cancellation?")
784+
}
785+
```
786+
787+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-46.kt)
788+
789+
The output of this code is:
790+
791+
```
792+
job1: I have my own context and execute independently!
793+
job2: I am a child of the request coroutine
794+
job1: I am not affected by cancellation of the request
795+
main: Who has survived request cancellation?
796+
```
797+
798+
### Combining contexts
799+
800+
Coroutine context can be combined using `+` operator. The context on the right-hand side replaces relevant entries
801+
of the context on the left-hand side. For example, a `Job` of the parent coroutine can be inherited, while
802+
its dispatcher replaced:
803+
804+
```kotlin
805+
fun main(args: Array<String>) = runBlocking<Unit> {
806+
// start a coroutine to process some kind of incoming request
807+
val request = launch(context) { // use the context of `runBlocking`
808+
// spawns CPU-intensive child job in CommonPool !!!
809+
val job = launch(context + CommonPool) {
810+
println("job: I am a child of the request coroutine, but with a different dispatcher")
811+
delay(1000)
812+
println("job: I will not execute this line if my parent request is cancelled")
813+
}
814+
job.join() // request completes when its sub-job completes
815+
}
816+
delay(500)
817+
request.cancel() // cancel processing of the request
818+
delay(1000) // delay a second to see what happens
819+
println("main: Who has survived request cancellation?")
820+
}
821+
```
822+
823+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-47.kt)
824+
825+
The expected outcome of this code is:
826+
827+
```
828+
job: I am a child of the request coroutine, but with a different dispatcher
829+
main: Who has survived request cancellation?
830+
```
831+
832+
### Naming coroutines for debugging
833+
834+
Automatically assignmed ids are good when coroutines log often and you just need to correlate log records
835+
coming from the same coroutine. However, when coroutine is tied to the processing of a specific request
836+
or doing some specific background task, it is better to name it explicitly for debugging purposes.
837+
Coroutine name serves the same function as a thread name. It'll get displayed in the thread name that
838+
is executing this coroutine when debugging more is turned on.
839+
840+
The following example demonstrates this concept:
841+
842+
```kotlin
843+
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
844+
845+
fun main(args: Array<String>) = runBlocking(CoroutineName("main")) {
846+
log("Started main coroutine")
847+
// run two background value computations
848+
val v1 = defer(CommonPool + CoroutineName("v1coroutine")) {
849+
log("Computing v1")
850+
delay(500)
851+
252
852+
}
853+
val v2 = defer(CommonPool + CoroutineName("v2coroutine")) {
854+
log("Computing v2")
855+
delay(1000)
856+
6
857+
}
858+
log("The answer for v1 / v2 = ${v1.await() / v2.await()}")
859+
}
860+
```
861+
862+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-48.kt)
863+
864+
The output it produces with `-Dkotlinx.coroutines.debug` JVM option is similar to:
865+
866+
```
867+
[main @main#1] Started main coroutine
868+
[ForkJoinPool.commonPool-worker-1 @v1coroutine#2] Computing v1
869+
[ForkJoinPool.commonPool-worker-2 @v2coroutine#3] Computing v2
870+
[main @main#1] The answer for v1 / v2 = 42
871+
```
558872

559873

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,20 @@ private val DEBUG = run {
1919
private val COROUTINE_ID = AtomicLong()
2020

2121
/**
22-
* A coroutine dispatcher that executes initial continuation of the coroutine _right here_ in the current call-frame
22+
* A coroutine dispatcher that is not confined to any specific thread.
23+
* It executes initial continuation of the coroutine _right here_ in the current call-frame
2324
* and let the coroutine resume in whatever thread that is used by the corresponding suspending function, without
2425
* mandating any specific threading policy.
2526
*/
26-
public object Here : CoroutineDispatcher() {
27+
public object Unconfined : CoroutineDispatcher() {
2728
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
2829
override fun dispatch(context: CoroutineContext, block: Runnable) { throw UnsupportedOperationException() }
2930
}
3031

32+
@Deprecated(message = "`Here` was renamed to `Unconfined`",
33+
replaceWith = ReplaceWith(expression = "Unconfined"))
34+
public typealias Here = Unconfined
35+
3136
/**
3237
* Creates context for the new coroutine with optional support for debugging facilities (when turned on).
3338
*

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import kotlin.coroutines.experimental.CoroutineContext
99
* Base class that shall be extended by all coroutine dispatcher implementations.
1010
*
1111
* The following standard implementations are provided by `kotlinx.coroutines`:
12-
* * [Here] -- starts coroutine execution _right here_ in the current call-frame until the first suspension. On first
13-
* suspension the coroutine builder function returns. The coroutine will resume in whatever thread that is used by the
14-
* corresponding suspending function, without mandating any specific threading policy.
12+
* * [Unconfined] -- starts coroutine execution in the current call-frame until the first suspension.
13+
* On first suspension the coroutine builder function returns.
14+
* The coroutine will resume in whatever thread that is used by the
15+
* corresponding suspending function, without confining it to any specific thread or pool.
1516
* This in an appropriate choice for IO-intensive coroutines that do not consume CPU resources.
1617
* * [CommonPool] -- immediately returns from the coroutine builder and schedules coroutine execution to
1718
* a common pool of shared background threads.

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,8 @@ internal open class DeferredCoroutine<T>(
8787
if (state is CompletedExceptionally) throw state.exception
8888
return state as T
8989
}
90+
91+
// for nicer debugging
92+
override fun toString(): String = "${javaClass.simpleName}{" +
93+
(if (isActive) "isActive=true" else "completed=${getState()}") + "}"
9094
}

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,9 @@ internal open class JobSupport : AbstractCoroutineContextElement(Job), Job {
346346
(handler as? JobNode)?.also { require(it.job === this) }
347347
?: InvokeOnCompletion(this, handler)
348348

349+
// for nicer debugging
350+
override fun toString(): String = "${javaClass.simpleName}{isActive=$isActive}"
351+
349352
/**
350353
* Marker interface for active [state][getState] of a job.
351354
*/

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Yield.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package kotlinx.coroutines.experimental
22

33
/**
44
* Yields a thread (or thread pool) of the current coroutine dispatcher to other coroutines to run.
5-
* If the coroutine dispatcher does not have its own thread pool (like [Here] dispatcher) then this
5+
* If the coroutine dispatcher does not have its own thread pool (like [Unconfined] dispatcher) then this
66
* function does nothing, but checks if the coroutine [Job] was completed.
77
* This suspending function is cancellable.
88
* If the [Job] of the current coroutine is completed when this suspending function is invoked or while

0 commit comments

Comments
 (0)