Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Coroutine Context - DAUG

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Coroutine Context - DAUG

Avatar for remcomokveld

remcomokveld

October 23, 2025
Tweet

More Decks by remcomokveld

Other Decks in Programming

Transcript

  1. 01 Why TestDispatchers? 04 Custom Context Element 02 Existing options

    05 Open Source Project 03 CoroutineContext Deep Dive Today's agenda DNA.inc Custom Coroutine Contexts
  2. Coroutine Context Why TestDispatcher? DNA.inc Production 1 2 3 4

    5 6 7 8 9 10 class val suspend fun ( network Network, ) { () User (Dispatchers.IO) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : = < > 5 "/user/me"
  3. Coroutine Context Why TestDispatcher? DNA.inc Production 1 2 3 4

    5 6 7 8 9 10 class val suspend fun ( network Network, ) { () User (Dispatchers.IO) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : = < > 5 "/user/me"
  4. Coroutine Context Why TestDispatcher? DNA.inc Production 1 2 3 4

    5 6 7 8 9 10 class val suspend fun ( network Network, ) { () User (Dispatchers.IO) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : = < > 5 "/user/me"
  5. Coroutine Context Why TestDispatcher? DNA.inc Test Production 1 2 3

    4 5 6 7 8 9 10 class val suspend fun ( network Network, ) { () User (Dispatchers.IO) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : = < > 5 "/user/me" 1 2 3 4 5 6 7 8 9 @Test = fun () runTest { } `test timeout`
  6. Coroutine Context Why TestDispatcher? DNA.inc Test Production 1 2 3

    4 5 6 7 8 9 10 class val suspend fun ( network Network, ) { () User (Dispatchers.IO) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : = < > 5 "/user/me" 1 2 3 4 5 6 7 8 9 10 @Test = = = = = < > fun val () runTest { repository ( network (delay .seconds), dispatcher (scheduler) ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository FakeNetwork StandardTestDispatcher loadUser 10
  7. Coroutine Context Why TestDispatcher? DNA.inc Test Production 1 2 3

    4 5 6 7 8 9 10 class val suspend fun ( network Network, ) { () User (Dispatchers.IO) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : = < > 5 "/user/me" 1 2 3 4 5 6 7 8 9 10 @Test = = = = = < > fun val () runTest { repository ( network (delay .seconds), dispatcher (scheduler) ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository FakeNetwork StandardTestDispatcher loadUser 10
  8. Coroutine Context Why TestDispatcher? DNA.inc Test Production 1 2 3

    4 5 6 7 8 9 10 class val suspend fun ( network Network, ) { () User (Dispatchers.IO) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : = < > 5 "/user/me" 1 2 3 4 5 6 7 8 9 10 @Test = = = = = < > fun val () runTest { repository ( network (delay .seconds), dispatcher (scheduler) ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository FakeNetwork StandardTestDispatcher loadUser 10
  9. Coroutine Context Why TestDispatcher? DNA.inc Test Production 1 2 3

    4 5 6 7 8 9 10 class val suspend fun ( network Network, ) { () User (Dispatchers.IO) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : = < > 5 "/user/me" 1 2 3 4 5 6 7 8 9 10 @Test = = = = = < > fun val () runTest { repository ( network (delay .seconds), dispatcher (scheduler) ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository FakeNetwork StandardTestDispatcher loadUser 10
  10. Coroutine Context Why TestDispatcher? DNA.inc Test passed 5 sec 92

    ms Total Test Production 1 2 3 4 5 6 7 8 9 10 class val suspend fun ( network Network, ) { () User (Dispatchers.IO) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : = < > 5 "/user/me" 1 2 3 4 5 6 7 8 9 10 @Test = = = = = < > fun val () runTest { repository ( network (delay .seconds), dispatcher (scheduler) ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository FakeNetwork StandardTestDispatcher loadUser 10
  11. Test Production 1 2 3 4 5 6 7 8

    9 10 class val suspend fun ( network Network, ) { () User (Dispatchers.IO) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : = < > 5 "/user/me" 1 2 3 4 5 6 7 8 9 10 @Test = = = = = < > fun val () runTest { repository ( network (delay .seconds), dispatcher (scheduler) ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository FakeNetwork StandardTestDispatcher loadUser 10 Coroutine Context Why TestDispatcher? DNA.inc Test passed 5 sec 92 ms Total
  12. Coroutine Context Why TestDispatcher? DNA.inc Test Production 1 2 3

    4 5 6 7 8 9 10 11 class val val suspend fun ( network Network, dispatcher CoroutineDispatcher, ) { () User (dispatcher) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : : = < > 5 "/user/me" 1 2 3 4 5 6 7 8 9 10 @Test = = = = = < > fun val () runTest { repository ( network (delay .seconds), dispatcher (scheduler) ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository FakeNetwork StandardTestDispatcher loadUser 10
  13. Coroutine Context Why TestDispatcher? DNA.inc Test passed 69 ms Total

    Test Production 1 2 3 4 5 6 7 8 9 10 11 class val val suspend fun ( network Network, dispatcher CoroutineDispatcher, ) { () User (dispatcher) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : : = < > 5 "/user/me" 1 2 3 4 5 6 7 8 9 10 @Test = = = = = < > fun val () runTest { repository ( network (delay .seconds), dispatcher (scheduler) ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository FakeNetwork StandardTestDispatcher loadUser 10
  14. Test Production Coroutine Context Why TestDispatcher? DNA.inc 1 2 3

    4 5 6 7 8 9 10 11 class val val suspend fun ( network Network, dispatcher CoroutineDispatcher, ) { () User (dispatcher) { ( .seconds) { parse User (network. ( )) } } } UserRepository loadUser withContext withTimeout get : : : = < > 5 "/user/me" 1 2 3 4 5 6 7 8 9 10 @Test = = = = = < > fun val () runTest { repository ( network (delay .seconds), dispatcher (scheduler) ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository FakeNetwork StandardTestDispatcher loadUser 10 Test passed 69 ms Total
  15. Coroutine Context Why TestDispatcher? 01 Faster tests because delays are

    skipped when the test thread becomes idle. 02 Eliminate real concurrency because all code actually runs
 on one thread. 03 Precise control of virtual time allows testing complex concurrency scenarios. DNA.inc
  16. Coroutine Context Options for replacing real dispatchers 01 Constructor/parameter injecting

    individual dispatchers. 02 Using Dispatchers.setMain 03 Using a parameter object and 
 DI framework. DNA.inc
  17. Options for replacing real dispatchers DNA.inc Injecting Dispatchers — Option

    1 CoroutineDispatcher (constructor) parameters Production 1 2 3 4 5 6 7 8 class private val suspend fun ( io CoroutineDispatcher Dispatchers.IO ) { () User (io) { } } UserRepository loadUser withContext : = : = /* fetch from network */
  18. Options for replacing real dispatchers DNA.inc Injecting Dispatchers — Option

    1 CoroutineDispatcher (constructor) parameters Real dispatcher can be replaced with TestDispatcher. Leaks implementation details (“this runs on IO”). Future churn: if you add CPU- bound work, you’ll need to add another dispatcher param. Usage of default parameter makes it easy to forget replacing it in test environments
  19. DNA.inc Options for replacing real dispatchers Injecting Dispatchers — Option

    2 setMain Test 1 2 3 4 5 6 7 8 9 10 11 @BeforeEach @AfterEach fun fun () { Dispatchers. ( () ) } () { Dispatchers. () } setUp setMain UnconfinedTestDispatcher tearDown resetMain
  20. DNA.inc Main dispatcher can be replaced with TestDispatcher No dispatcher

    params needed. Implementation chooses the dispatcher it needs, when it needs it. Only affects Main, not IO/Default. Global mutable state; risky in parallel tests. Options for replacing real dispatchers Injecting Dispatchers — Option 2 setMain
  21. DNA.inc Options for replacing real dispatchers Injecting Dispatchers — Option

    3 DispatcherProvider Test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface val companion object override val get class val override val get DispatchersProvider { io CoroutineDispatcher Default DispatchersProvider { io () Dispatchers.IO } } ( scope TestScope ) DispatchersProvider { io CoroutineDispatcher () (scope.testScheduler) } : : = : : : = TestDispatchers StandardTestDispatcher
  22. DNA.inc Options for replacing real dispatchers Injecting Dispatchers — Option

    3 DispatcherProvider Test 1 2 3 4 5 6 7 8 class private val suspend fun ( dispatchers DispatcherProvider ) { () User (dispatchers.io) { } } UserRepository loadUser withContext : : = /* fetch from network */
  23. Options for replacing real dispatchers DNA.inc Real dispatcher can be

    replaced with TestDispatcher Cleaner constructor than many individual params. Implementation chooses the dispatcher it needs, when it needs it. Extra DI surface threaded everywhere Injecting Dispatchers — Option 3 DispatcherProvider
  24. DNA.inc How is the context defined? Map-like API Simplified Framework

    1 2 3 4 5 6 7 8 9 10 11 12 interface operator fun get operator fun interface val interface CoroutineContext { E Element (key Key E ) E (context CoroutineContext) CoroutineContext Element CoroutineContext { key Key } Key E Element } < : > : < > : ? : : : : <*> < : > plus
  25. DNA.inc How is the context defined? Map-like get method Simplified

    Framework 1 2 3 4 5 6 7 8 9 10 11 12 interface operator fun get operator fun interface val interface CoroutineContext { E Element (key Key E ) E (context CoroutineContext) CoroutineContext Element CoroutineContext { key Key } Key E Element } < : > : < > : ? : : : : <*> < : > plus
  26. DNA.inc How is the context defined? Map-like plus Simplified Framework

    1 2 3 4 5 6 7 8 9 10 11 12 interface operator fun get operator fun interface val interface CoroutineContext { E Element (key Key E ) E (context CoroutineContext) CoroutineContext Element CoroutineContext { key Key } Key E Element } < : > : < > : ? : : : : <*> < : > plus
  27. DNA.inc How is the context defined? Map-like API Simplified Framework

    1 2 3 4 5 6 7 8 9 10 11 12 interface operator fun get operator fun interface val interface CoroutineContext { E Element (key Key E ) E (context CoroutineContext) CoroutineContext Element CoroutineContext { key Key } Key E Element } < : > : < > : ? : : : : <*> < : > plus
  28. DNA.inc How is the context defined? Key defines it’s Element

    type Simplified Framework 1 2 3 4 5 6 7 8 9 10 11 12 interface operator fun get operator fun interface val interface CoroutineContext { E Element (key Key E ) E (context CoroutineContext) CoroutineContext Element CoroutineContext { key Key } Key E Element } < : > : < > : ? : : : : <*> < : > plus
  29. DNA.inc How is the context defined? Element defines it’s Key

    Simplified Framework 1 2 3 4 5 6 7 8 9 10 11 12 interface operator fun get operator fun interface val interface CoroutineContext { E Element (key Key E ) E (context CoroutineContext) CoroutineContext Element CoroutineContext { key Key } Key E Element } < : > : < > : ? : : : : <*> < : > plus
  30. DNA.inc How is the context defined? Elements are Contexts Simplified

    Framework 1 2 3 4 5 6 7 8 9 10 11 12 interface operator fun get operator fun interface val interface CoroutineContext { E Element (key Key E ) E (context CoroutineContext) CoroutineContext Element CoroutineContext { key Key } Key E Element } < : > : < > : ? : : : : <*> < : > plus
  31. Example 1 2 3 4 5 6 7 8 data

    class val override val companion object ( name String ) CoroutineContext. { key CoroutineContext.Key CoroutineName CoroutineContext.Key CoroutineName } CoroutineName Element : : : <*> = : < > DNA.inc How is the context defined? Example Context Element
  32. DNA.inc How is the context defined? Example Context Element Example

    1 2 3 4 5 6 7 8 data class val override val companion object ( name String ) CoroutineContext. { key CoroutineContext.Key CoroutineName CoroutineContext.Key CoroutineName } CoroutineName Element : : : <*> = : < >
  33. DNA.inc How is the context defined? Example Context Element Example

    1 2 3 4 5 6 7 8 data class val override val companion object ( name String ) CoroutineContext. { key CoroutineContext.Key CoroutineName CoroutineContext.Key CoroutineName } CoroutineName Element : : : <*> = : < >
  34. DNA.inc How is the context defined? Example Context Element Example

    1 2 3 4 5 6 7 8 data class val override val companion object ( name String ) CoroutineContext. { key CoroutineContext.Key CoroutineName CoroutineContext.Key CoroutineName } CoroutineName Element : : : <*> = : < >
  35. DNA.inc How is the context defined? Example Context Element Example

    1 2 3 4 5 6 7 val val val context1 ( ) context2 ( ) context3 context1. (context2) (context1[CoroutineName] .name) (context2[CoroutineName] .name) (context3[CoroutineName] .name) = = = ? ? ? CoroutineName CoroutineName plus println println println "Hello World!" "Hello DAUG!" // prints "Hello World!" // prints "Hello DAUG!" // prints "Hello DAUG!"
  36. How is the context defined? CoroutineContext API • • •

    • CoroutineContext is like an immutable Map of elements. All elements that make up a coroutine context are a coroutine context themselves. The key of every element defines the type (class or interface) of that element. CoroutineContexts can easily be combined together DNA.inc
  37. Coroutine Context Accessing the Coroutine Context • • Property on

    a CoroutineScope Top-level suspend val coroutineContext: CoroutineContext in any suspending method. DNA.inc
  38. DNA.inc Accessing the Coroutine Context Top-level Coroutine Context property Source

    Code 1 2 3 4 suspend fun val return () String { name coroutineContext[CoroutineName] .name $name } createGreeting : = ? ?: "World" "Hello !"
  39. DNA.inc Accessing the Coroutine Context Top-level Coroutine Context property Decompiled

    Bytecode 1 2 3 4 fun val (continuation Continuation String ) { name continuation.context[CoroutineName] .name continuation. ("Hello $name ”) } createGreeting resume : < > = ? ?: ! "World" Source Code 1 2 3 4 suspend fun val return () String { name coroutineContext[CoroutineName] .name $name } createGreeting : = ? ?: "World" "Hello !"
  40. CoroutineContext Inheritance launch {} method Simplified Framework 1 2 3

    4 5 6 7 8 9 10 11 12 13 14 15 16 fun suspend CoroutineScope. ( context CoroutineContext EmptyCoroutineContext, block CoroutineScope.() Unit ) Job { } launch : = : -> : DNA.inc
  41. CoroutineContext Inheritance launch {} method DNA.inc Simplified Framework 1 2

    3 4 5 6 7 8 9 10 11 12 13 14 15 16 fun suspend val this return CoroutineScope. ( context CoroutineContext EmptyCoroutineContext, block CoroutineScope.() Unit ) Job { childJob (parent context[Job] .coroutineContext[Job]) childJob } launch Job : = : -> : = = ?:
  42. CoroutineContext Inheritance launch {} method DNA.inc Simplified Framework 1 2

    3 4 5 6 7 8 9 10 11 12 13 14 15 16 fun suspend val this val this return CoroutineScope. ( context CoroutineContext EmptyCoroutineContext, block CoroutineScope.() Unit ) Job { childJob (parent context[Job] .coroutineContext[Job]) newContext .coroutineContext context childJob childJob } launch Job : = : -> : = = ?: = + +
  43. CoroutineContext Inheritance launch {} method DNA.inc Simplified Framework 1 2

    3 4 5 6 7 8 9 10 11 12 13 14 15 16 fun suspend val this val this val return CoroutineScope. ( context CoroutineContext EmptyCoroutineContext, block CoroutineScope.() Unit ) Job { childJob (parent context[Job] .coroutineContext[Job]) newContext .coroutineContext context childJob newScope (newContext) childJob } launch Job CoroutineScope : = : -> : = = ?: = + + =
  44. CoroutineContext Inheritance launch {} method DNA.inc Simplified Framework 1 2

    3 4 5 6 7 8 9 10 11 12 13 14 15 16 fun suspend val this val this val val return CoroutineScope. ( context CoroutineContext EmptyCoroutineContext, block CoroutineScope.() Unit ) Job { childJob (parent context[Job] .coroutineContext[Job]) newContext .coroutineContext context childJob newScope (newContext) dispatcher newContext[CoroutineDispatcher] Dispatchers.Default childJob } launch Job CoroutineScope : = : -> : = = ?: = + + = = ?:
  45. CoroutineContext Inheritance launch {} method DNA.inc Simplified Framework 1 2

    3 4 5 6 7 8 9 10 11 12 13 14 15 16 fun suspend val this val this val val return CoroutineScope. ( context CoroutineContext EmptyCoroutineContext, block CoroutineScope.() Unit ) Job { childJob (parent context[Job] .coroutineContext[Job]) newContext .coroutineContext context childJob newScope (newContext) dispatcher newContext[CoroutineDispatcher] Dispatchers.Default dispatcher. (newContext) { } childJob } launch Job CoroutineScope dispatch : = : -> : = = ?: = + + = = ?:
  46. CoroutineContext Inheritance launch {} method DNA.inc Simplified Framework 1 2

    3 4 5 6 7 8 9 10 11 12 13 14 15 16 fun suspend val this val this val val return CoroutineScope. ( context CoroutineContext EmptyCoroutineContext, block CoroutineScope.() Unit ) Job { childJob (parent context[Job] .coroutineContext[Job]) newContext .coroutineContext context childJob newScope (newContext) dispatcher newContext[CoroutineDispatcher] Dispatchers.Default dispatcher. (newContext) { runCatching { block. (newScope) } } childJob } launch Job CoroutineScope dispatch invoke : = : -> : = = ?: = + + = = ?:
  47. CoroutineContext Inheritance launch {} method DNA.inc Simplified Framework 1 2

    3 4 5 6 7 8 9 10 11 12 13 14 15 16 fun suspend val this val this val val return CoroutineScope. ( context CoroutineContext EmptyCoroutineContext, block CoroutineScope.() Unit ) Job { childJob (parent context[Job] .coroutineContext[Job]) newContext .coroutineContext context childJob newScope (newContext) dispatcher newContext[CoroutineDispatcher] Dispatchers.Default dispatcher. (newContext) { runCatching { block. (newScope) } . { childJob. () } . { childJob. (it) } } childJob } launch Job CoroutineScope dispatch invoke onSuccess complete onFailure completeExceptionally : = : -> : = = ?: = + + = = ?:
  48. CoroutineContext Inheritance launch {} method DNA.inc Simplified Framework 1 2

    3 4 5 6 7 8 9 10 11 12 13 14 15 16 fun suspend val this val this val val return CoroutineScope. ( context CoroutineContext EmptyCoroutineContext, block CoroutineScope.() Unit ) Job { childJob (parent context[Job] .coroutineContext[Job]) newContext .coroutineContext context childJob newScope (newContext) dispatcher newContext[CoroutineDispatcher] Dispatchers.Default dispatcher. (newContext) { runCatching { block. (newScope) } . { childJob. () } . { childJob. (it) } } childJob } launch Job CoroutineScope dispatch invoke onSuccess complete onFailure completeExceptionally : = : -> : = = ?: = + + = = ?:
  49. DNA.inc CoroutineContext Inheritance Inheritance Tree Test 1 2 3 4

    5 6 7 runBlocking CoroutineName withContext async withContext ( ( )) { (Dispatchers.IO) { async { } (Dispatchers.Unconfined) {} } (Dispatchers.Main) {} } "My Test"
  50. DNA.inc CoroutineContext Inheritance Tree Test 1 2 3 4 5

    6 7 runBlocking CoroutineName withContext async withContext ( ( )) { (Dispatchers.IO) { async { } (Dispatchers.Unconfined) {} } (Dispatchers.Main) {} } "My Test" parent of Job#2 parent of Job#3 Job#1 runBlocking{} Job=Job#1 Dispatcher=Default Name=My test Start withContext(Dispatchers.IO) Job=Job#1 Dispatcher=IO Name=My test withContext(Dispatchers.Main) Job=Job#1 Dispatcher=Main Name=My test async(Dispatchers.Unconfined) Job=Job#3 Dispatcher=Unconfined Name=My test async{} Job=Job#2 Dispatcher=I0 Name=My test
  51. DNA.inc CoroutineContext Inheritance Tree Test 1 2 3 4 5

    6 7 runBlocking CoroutineName withContext async withContext ( ( )) { (Dispatchers.IO) { async { } (Dispatchers.Unconfined) {} } (Dispatchers.Main) {} } "My Test" parent of Job#2 parent of Job#3 Job#1 runBlocking{} Job=Job#1 Dispatcher=Default Name=My test Start withContext(Dispatchers.IO) Job=Job#1 Dispatcher=IO Name=My test withContext(Dispatchers.Main) Job=Job#1 Dispatcher=Main Name=My test async(Dispatchers.Unconfined) Job=Job#3 Dispatcher=Unconfined Name=My test async{} Job=Job#2 Dispatcher=I0 Name=My test
  52. DNA.inc CoroutineContext Inheritance Tree runBlocking{} Job=Job#1 Dispatcher=Default Name=My test Start

    withContext(Dispatchers.IO) Job=Job#1 Dispatcher=IO Name=My test withContext(Dispatchers.Main) Job=Job#1 Dispatcher=Main Name=My test async(Dispatchers.Unconfined) Job=Job#3 Dispatcher=Unconfined Name=My test async{} Job=Job#2 Dispatcher=I0 Name=My test parent of Job#2 parent of Job#3 Job#1 Test 1 2 3 4 5 6 7 runBlocking CoroutineName withContext async withContext ( ( )) { (Dispatchers.IO) { async { } (Dispatchers.Unconfined) {} } (Dispatchers.Main) {} } "My Test"
  53. DNA.inc CoroutineContext Inheritance Tree Test 1 2 3 4 5

    6 7 runBlocking CoroutineName withContext async withContext ( ( )) { (Dispatchers.IO) { async { } (Dispatchers.Unconfined) {} } (Dispatchers.Main) {} } "My Test" parent of Job#2 parent of Job#3 Job#1 runBlocking{} Job=Job#1 Dispatcher=Default Name=My test Start withContext(Dispatchers.IO) Job=Job#1 Dispatcher=IO Name=My test withContext(Dispatchers.Main) Job=Job#1 Dispatcher=Main Name=My test async(Dispatchers.Unconfined) Job=Job#3 Dispatcher=Unconfined Name=My test async{} Job=Job#2 Dispatcher=I0 Name=My test
  54. parent of Job#2 parent of Job#3 Job#1 runBlocking{} Job=Job#1 Dispatcher=Default

    Name=My test Start withContext(Dispatchers.IO) Job=Job#1 Dispatcher=IO Name=My test withContext(Dispatchers.Main) Job=Job#1 Dispatcher=Main Name=My test async(Dispatchers.Unconfined) Job=Job#3 Dispatcher=Unconfined Name=My test async{} Job=Job#2 Dispatcher=I0 Name=My test DNA.inc CoroutineContext Inheritance Tree Test 1 2 3 4 5 6 7 runBlocking CoroutineName withContext async withContext ( ( )) { (Dispatchers.IO) { async { } (Dispatchers.Unconfined) {} } (Dispatchers.Main) {} } "My Test"
  55. DNA.inc CoroutineContext Inheritance Tree Test 1 2 3 4 5

    6 7 runBlocking CoroutineName withContext async withContext ( ( )) { (Dispatchers.IO) { async { } (Dispatchers.Unconfined) {} } (Dispatchers.Main) {} } "My Test" parent of Job#2 parent of Job#3 Job#1 runBlocking{} Job=Job#1 Dispatcher=Default Name=My test Start withContext(Dispatchers.IO) Job=Job#1 Dispatcher=IO Name=My test withContext(Dispatchers.Main) Job=Job#1 Dispatcher=Main Name=My test async(Dispatchers.Unconfined) Job=Job#3 Dispatcher=Unconfined Name=My test async{} Job=Job#2 Dispatcher=I0 Name=My test
  56. DNA.inc CoroutineContext Inheritance Tree Test 1 2 3 4 5

    6 7 runBlocking CoroutineName withContext async withContext ( ( )) { (Dispatchers.IO) { async { } (Dispatchers.Unconfined) {} } (Dispatchers.Main) {} } "My Test" parent of Job#2 parent of Job#3 Job#1 runBlocking{} Job=Job#1 Dispatcher=Default Name=My test Start withContext(Dispatchers.IO) Job=Job#1 Dispatcher=IO Name=My test withContext(Dispatchers.Main) Job=Job#1 Dispatcher=Main Name=My test async(Dispatchers.Unconfined) Job=Job#3 Dispatcher=Unconfined Name=My test async{} Job=Job#2 Dispatcher=I0 Name=My test
  57. DNA.inc CoroutineContext Inheritance Tree Test 1 2 3 4 5

    6 7 8 runBlocking CoroutineName withContext CoroutineScope launch async withContext ( ( )) { (Dispatchers.IO) { (Dispatchers.IO). { } async { } (Dispatchers.Unconfined) {} } (Dispatchers.Main) {} } "My Test" parent of Job#2 parent of Job#3 Job#1 withContext(Dispatchers.IO) Job=Job#1 Dispatcher=IO Name=My test launch(Dispatchers.IO){} Job=Job#4 Dispatcher=IO withContext(Dispatchers.Main) Job=Job#1 Dispatcher=Main Name=My test async(Dispatchers.Unconfined) Job=Job#3 Dispatcher=Unconfined Name=My test async{} Job=Job#2 Dispatcher=I0 Name=My test runBlocking{} Job=Job#1 Dispatcher=Default Name=My test Start
  58. DNA.inc CoroutineContext Inheritance Tree Test 1 2 3 4 5

    6 7 8 runBlocking CoroutineName withContext CoroutineScope launch async withContext ( ( )) { (Dispatchers.IO) { (Dispatchers.IO). { } async { } (Dispatchers.Unconfined) {} } (Dispatchers.Main) {} } "My Test" parent of Job#2 parent of Job#3 Job#1 withContext(Dispatchers.IO) Job=Job#1 Dispatcher=IO Name=My test launch(Dispatchers.IO){} Job=Job#4 Dispatcher=IO withContext(Dispatchers.Main) Job=Job#1 Dispatcher=Main Name=My test async(Dispatchers.Unconfined) Job=Job#3 Dispatcher=Unconfined Name=My test async{} Job=Job#2 Dispatcher=I0 Name=My test runBlocking{} Job=Job#1 Dispatcher=Default Name=My test Start
  59. CoroutineContext Deep Dive Recap 01 CoroutineContext is a Map-like API

    for storing key-value pairs 02 Suspending methods can access the caller’s context through Continuation 03 CoroutineContext Elements are copied to child-coroutines DNA.inc
  60. Production Custom Context Element Implementing a Custom Element DNA.inc 1

    2 3 4 5 6 interface val companion object DispatchersContextElement CoroutineContext. { provider DispatchersProvider CoroutineContext.Key DispatchersContextElement } : : : < > Element
  61. Production Custom Context Element Implementing a Custom Element DNA.inc 1

    2 3 4 5 6 val get this suspend fun CoroutineContext.dispatchers DispatchersProvider () [DispatchersContextElement] .provider RealDispatchersProvider () DispatchersProvider coroutineContext.dispatchers : = ? ?: : = currentDispatchers
  62. Production Custom Context Element Implementing a Custom Element DNA.inc 1

    2 3 4 5 6 val get this suspend fun CoroutineContext.dispatchers DispatchersProvider () [DispatchersContextElement] .provider RealDispatchersProvider () DispatchersProvider coroutineContext.dispatchers : = ? ?: : = currentDispatchers
  63. Production Custom Context Element Implementing a Custom Element DNA.inc 1

    2 3 4 5 6 val get this suspend fun CoroutineContext.dispatchers DispatchersProvider () [DispatchersContextElement] .provider RealDispatchersProvider () DispatchersProvider coroutineContext.dispatchers : = ? ?: : = currentDispatchers
  64. Production Custom Context Element Implementing a Custom Element DNA.inc 1

    2 3 4 5 6 val get this suspend fun CoroutineContext.dispatchers DispatchersProvider () [DispatchersContextElement] .provider RealDispatchersProvider () DispatchersProvider coroutineContext.dispatchers : = ? ?: : = currentDispatchers
  65. DNA.inc Custom Context Element Injecting Dispatchers — Option 4 Use

    CoroutineContext Production 1 2 3 4 5 6 class suspend fun UserRepository { () User ( ().io) { } } loadUser withContext currentDispatchers : = /* fetch from network */
  66. DNA.inc Custom Context Element Injecting Dispatchers — Option 4 Use

    CoroutineContext Production 1 2 3 4 5 6 class suspend fun UserRepository { () User ( ().io) { } } loadUser withContext currentDispatchers : = /* fetch from network */ Test 1 2 3 4 5 6 7 8 9 @Test = = = = < > fun val () runTest { repository ( network (delay .seconds), ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository FakeNetwork loadUser 10
  67. Test 1 2 3 4 5 6 7 8 9

    @Test = = -> < > fun val () runTest { repository ( Network { url () } ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository awaitCancellation loadUser DNA.inc Custom Context Element Injecting Dispatchers — Option 4 Use CoroutineContext
  68. Test 1 2 3 4 5 6 7 8 9

    @Test = = -> < > fun val () ( ()) { repository ( Network { url () } ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` runTest createTestContext UserRepository awaitCancellation loadUser DNA.inc Custom Context Element Injecting Dispatchers — Option 4 Use CoroutineContext
  69. Test 1 2 3 4 5 6 7 8 9

    @Test = = -> < > fun val () ( ()) { repository ( Network { url () } ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` runTest createTestContext UserRepository awaitCancellation loadUser DNA.inc Custom Context Element Injecting Dispatchers — Option 4 Use CoroutineContext
  70. Test 1 2 3 4 5 6 7 8 9

    @Test = = -> < > fun val () runTest { repository ( Network { url () } ) assertThrows TimeoutCancellationException { repository. () } } `test timeout` UserRepository awaitCancellation loadUser DNA.inc Custom Context Element Injecting Dispatchers — Option 4 Use CoroutineContext
  71. Test 1 2 3 4 5 6 7 8 9

    10 11 import fun val inc.dna.coroutines.dispatchers.test.runTest () runTest { repository ( Network { url () } ) assertThrows TimeoutCancellationException { repository. () } } @Test = = -> < > `test timeout` UserRepository awaitCancellation loadUser DNA.inc Custom Context Element Injecting Dispatchers — Option 4 Use CoroutineContext
  72. DNA.inc Custom Context Element Injecting Dispatchers — Option 4 Use

    CoroutineContext Real dispatcher can be replaced with TestDispatcher No dispatcher params. Implementation chooses the dispatcher it needs, when it needs it. Does not feel bolted on. Some understanding of CoroutineContext inheritance.
  73. Custom Context Element CoroutineContext as Service Locator Rules • •

    • Classes that do not have a CoroutineScope don’t need any constructor injection. Classes that create a CoroutineScope need to constructor inject a CoroutineContext and tests pass the context of the TestScope. For classes that constructor inject a CoroutineScope the test should pass a TestScope, backgroundScope or a custom scope created with the TestScope’s Context DNA.inc