@@ -22,6 +22,15 @@ This is a short guide on core features of `kotlinx.coroutines` with a series of
22
22
* [ Sequential by default] ( #sequential-by-default )
23
23
* [ Concurrent using deferred value] ( #concurrent-using-deferred-value )
24
24
* [ 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 )
25
34
26
35
<!-- - KNIT kotlinx-coroutines-core/src/test/kotlin/guide/.*\.kt -->
27
36
@@ -555,5 +564,310 @@ So, we are back to two sequential execution, because we _first_ await for the `o
555
564
for the second one. It is not the intended use-case for ` lazyDefer ` . It is designed as a replacement for
556
565
the standard ` lazy ` function in cases when computation of the value involve suspending functions.
557
566
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
+ ```
558
872
559
873
0 commit comments