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

Understanding CoroutineContext

Understanding CoroutineContext

These slides are from a lightning talk I gave at the Chicago Kotlin Users Group.

The properties of CoroutineContext are handled through a pattern called a type-safe heterogeneous map. This means that it has fantastic extensibility, but it's pretty difficult to determine what's going on by just browsing the source code.

This talk is a quick attempt at describing a different mental model for the CoroutineContext so that we might better understand how it works.

Rick Busarow

May 22, 2019
Tweet

More Decks by Rick Busarow

Other Decks in Programming

Transcript

  1. @rbusarow public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart

    = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() !-> Unit ): Job {~ val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }~ Builders.common
  2. @rbusarow public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart

    = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() !-> Unit ): Job {~ val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }~ Builders.common
  3. @rbusarow public fun~CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart =

    CoroutineStart.DEFAULT, block: suspend CoroutineScope.() !->~Unit ):~Job {~ val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }~ Builders.common
  4. @rbusarow Builders.common public suspend fun <T> withContext( context: CoroutineContext, block:

    suspend CoroutineScope.() !-> T ): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont !-> … }
  5. @rbusarow Builders.common public suspend fun <T> withContext( context: CoroutineContext, block:

    suspend CoroutineScope.() !-> T ): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont !-> … }
  6. @rbusarow interface Element :~CoroutineContext interface Job :~CoroutineContext.Element interface ContinuationInterceptor :~CoroutineContext.Element

    interface CoroutineExceptionHandler :~CoroutineContext.Element data class CoroutineName( val name: String ) : CoroutineContext.Element
  7. @rbusarow CoroutineContext.kt interface CoroutineContext {~ operator fun <E :~Element> get(key:

    Key<E>): E? operator fun plus(context:~CoroutineContext): CoroutineContext {~…~} fun <R> fold(initial: R, operation: (R, Element) !-> R): R fun minusKey(key: Key!!<*>): CoroutineContext interface Key<E :~Element> interface Element :~CoroutineContext {~…~} }~
  8. @rbusarow CoroutineContext.kt interface CoroutineContext {~ operator fun <E :~Element> get(key:

    Key<E>): E? operator fun plus(context:~CoroutineContext): CoroutineContext {~…~} fun <R> fold(initial: R, operation: (R, Element) !-> R): R fun minusKey(key: Key!!<*>): CoroutineContext interface Key<E :~Element> interface Element :~CoroutineContext {~…~} }~
  9. @rbusarow CoroutineContext.kt interface CoroutineContext { !// … interface Element :

    CoroutineContext { !// … override operator fun <E : Element> get(key: Key<E>): E? = @Suppress("UNCHECKED_CAST") if (this.key !== key) this as E else null override fun <R> fold( initial: R, operation: (R, Element) !-> R ): R = operation(initial, this) override fun minusKey(key: Key!!<*>): CoroutineContext = if (this.key !== key) EmptyCoroutineContext else this } }
  10. @rbusarow CoroutineContext.kt interface CoroutineContext {~ operator fun <E :~Element> get(key:

    Key<E>): E? operator fun plus(context:~CoroutineContext): CoroutineContext {~…~} fun <R> fold(initial: R, operation: (R, Element) !-> R): R fun minusKey(key: Key!!<*>): CoroutineContext interface Key<E :~Element> interface Element :~CoroutineContext {~…~} }~
  11. @rbusarow CoroutineContext.kt interface CoroutineContext {~ operator fun <E :~Element> get(key:

    Key<E>): E? operator~fun~plus(context:~CoroutineContext):~CoroutineContext {~…~} fun <R> fold(initial: R, operation: (R, Element) !->~R): R fun minusKey(key: Key!!<*>): CoroutineContext interface Key<E :~Element> interface Element :~CoroutineContext {~…~} }~
  12. @rbusarow CoroutineContext.kt operator~fun~plus(context:~CoroutineContext):~CoroutineContext = if (context !!=== EmptyCoroutineContext) this else

    context.fold(this) { acc, element !-> val removed = acc.minusKey(element.key) if (removed !!=== EmptyCoroutineContext) element else { val interceptor = removed[ContinuationInterceptor] if (interceptor !== null) CombinedContext(removed, element) else { val left = removed.minusKey(ContinuationInterceptor) if (left !!=== EmptyCoroutineContext) CombinedContext( element, interceptor ) else CombinedContext(CombinedContext(left, element), interceptor) } } }
  13. @rbusarow open class SimpleCoroutineContext( val job: Job =~Job(), val continuationInterceptor:

    ContinuationInterceptor =~ Dispatchers.Default, val coroutineExceptionHandler: CoroutineExceptionHandler =~ DefaultCoroutineExceptionHandler(), val coroutineName: CoroutineName =~CoroutineName("coroutine") )~
  14. @rbusarow open class SimpleCoroutineContext( val job: Job =~Job(), val continuationInterceptor:

    ContinuationInterceptor =~ Dispatchers.Default, val coroutineExceptionHandler: CoroutineExceptionHandler =~ DefaultCoroutineExceptionHandler(), val coroutineName: CoroutineName =~CoroutineName(“coroutine") )~ { operator fun plus(other: CoroutineContext): SimpleCoroutineContext { return when (other) { is Job !-> SimpleCoroutineContext(job = other, continuationInterceptor = continuationInterceptor, coroutineExceptionHandler = coroutineExceptionHandler, coroutineName = coroutineName ) else !-> TODO(“definitely won’t fit on a slide") } } }
  15. @rbusarow class MyCustomElement(val name: String) : CoroutineContext.Element {~ override val

    key: Key!!<*> get() =~MyCustomElement companion object :~Key<MyCustomElement> }~
  16. @rbusarow CoroutineContext.kt interface CoroutineContext {~ operator fun <E :~Element> get(key:

    Key<E>): E? operator~fun~plus(context:~CoroutineContext):~CoroutineContext {~…~} fun <R> fold(initial: R, operation: (R, Element) !->~R): R fun minusKey(key: Key!!<*>): CoroutineContext interface Key<E :~Element> interface Element :~CoroutineContext {~…~} }~
  17. @rbusarow CoroutineContext.kt public interface CoroutineContext { !// … public interface

    Element : CoroutineContext { public val key: Key!!<*> public override operator fun <E : Element> get(key: Key<E>): E? = @Suppress("UNCHECKED_CAST") if (this.key !== key) this as E else null public override fun <R> fold( initial: R, operation: (R, Element) !-> R ): R = operation(initial, this) public override fun minusKey(key: Key!!<*>): CoroutineContext = if (this.key !== key) EmptyCoroutineContext else this } }
  18. @rbusarow Builders.common public suspend fun <T> withContext( context: CoroutineContext, block:

    suspend CoroutineScope.() !-> T ): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont !-> val oldContext = uCont.context val newContext = oldContext + context !// … }
  19. @rbusarow Builders.common public suspend fun <T> withContext( context: CoroutineContext, block:

    suspend CoroutineScope.() !-> T ): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont !-> val oldContext = uCont.context val newContext = oldContext + context !// … }
  20. @rbusarow fun CoroutineScope.launchThenDoSomethingSlow() = launch { doSomethingSlow() } /** *

    This code always inherits: * 1. Job * 2. CoroutineExceptionHandler * 3. CoroutineName * * This code enforces its own: * 1. ContinuationInterceptor !*/ suspend fun doSomethingSlow() = withContext(Dispatchers.IO) { !// !!... }
  21. @rbusarow fun CoroutineScope.launchThenDoSomethingSlow() = launch { doSomethingSlow() } /** *

    This code always inherits: * 1. CoroutineExceptionHandler * 2. CoroutineName * * This code enforces its own: * 1. Job * 2. ContinuationInterceptor !*/ suspend fun doSomethingSlow() = withContext(Job() + Dispatchers.IO) { !// !!... }