@@ -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
555564for the second one. It is not the intended use-case for ` lazyDefer ` . It is designed as a replacement for
556565the 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
0 commit comments