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

Learning Concurrency in Kotlin - Context switching

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

Learning Concurrency in Kotlin - Context switching

"Learning Concurrency in Kotlin: Build highly efficient and robust applications"

https://www.amazon.com/Learning-Concurrency-Kotlin-efficient-applications-ebook/dp/B0791HNZ4J

Avatar for Samuel Huang

Samuel Huang

April 10, 2020

More Decks by Samuel Huang

Other Decks in Programming

Transcript

  1. State Machine suspend fun loginUser(userId: String, password: String): User {

    val user = userRemoteDataSource.logUserIn(userId, password) val userDb = userLocalDataSource.logUserIn(user) return userDb } 3
  2. State Machine fun loginUser(userId: String, password: String, completion: Continuation<Any?>) {

    when (label) { 0 -> { // Label 0 -> first execution userRemoteDataSource.logUserIn(userId, password) } 1 -> { // Label 1 -> resumes from userRemoteDataSource userLocalDataSource.logUserIn(user) } 2 -> { // Label 2 -> resumes from userLocalDataSource completion.resume(userDb) } else -> throw IllegalStateException(...) } } 4
  3. CoroutineContext public interface CoroutineContext { public operator fun <E :

    Element> get(key: Key<E>): E? public interface Key<E : Element> public interface Element : CoroutineContext } It’s made of: • Job — controls the lifecycle of the coroutine. • CoroutineDispatcher — dispatches work to the appropriate thread. • CoroutineName — name of the coroutine. • CoroutineExceptionHandler — handles uncaught exceptions. 6 val job = context[Job]
  4. CoroutineDispatcher public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { public

    open fun isDispatchNeeded(context: CoroutineContext): Boolean = true public abstract fun dispatch(context: CoroutineContext, block: Runnable) public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation) } 8
  5. CoroutineDispatcher public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { public

    open fun isDispatchNeeded(context: CoroutineContext): Boolean = true public abstract fun dispatch(context: CoroutineContext, block: Runnable) public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation) } 9
  6. CoroutineDispatcher public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { public

    open fun isDispatchNeeded(context: CoroutineContext): Boolean = true public abstract fun dispatch(context: CoroutineContext, block: Runnable) public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation) } 10
  7. Runnable public expect interface Runnable { public fun run() }

    “Using the keyword expect you can define an API to be implemented on each platform.” 11
  8. Runnable public expect interface Runnable { public fun run() }

    “Using the keyword expect you can define an API to be implemented on each platform.” For JVM public actual typealias Runnable = java.lang.Runnable “The implementation of an expected declaration is marked with the keyword actual.” 12
  9. CoroutineDispatcher public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { public

    open fun isDispatchNeeded(context: CoroutineContext): Boolean = true public abstract fun dispatch(context: CoroutineContext, block: Runnable) public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation) } Let’s see some important CoroutineDispatcher 13
  10. Dispatchers(1/3) CommonPool CoroutineDispatcher.kt public open fun isDispatchNeeded(context: CoroutineContext): Boolean =

    true CommonPool.kt override fun dispatch(context: CoroutineContext, block: Runnable) { try { (pool ?: getOrCreatePoolSync()).execute(wrapTask(block)) } catch (e: RejectedExecutionException) { unTrackTask() DefaultExecutor.enqueue(block) } } 14
  11. Dispatchers(1/3) CommonPool CoroutineDispatcher.kt public open fun isDispatchNeeded(context: CoroutineContext): Boolean =

    true CommonPool.kt override fun dispatch(context: CoroutineContext, block: Runnable) { try { (pool ?: getOrCreatePoolSync()).execute(wrapTask(block)) } catch (e: RejectedExecutionException) { unTrackTask() DefaultExecutor.enqueue(block) } } @Synchronized private fun getOrCreatePoolSync(): Executor = pool ?: createPool().also { pool = it } 15 • ForkJoinPool • newFixedThreadPool()
  12. Dispatchers(2/3) Unconfined override fun isDispatchNeeded(context: CoroutineContext): Boolean = false override

    fun dispatch(context: CoroutineContext, block: Runnable) { throw UnsupportedOperationException() } 17
  13. Dispatchers(3/3) Android's UI internal class HandlerContext private constructor( private val

    handler: Handler, private val name: String?, private val invokeImmediately: Boolean ) : HandlerDispatcher(), Delay { override fun isDispatchNeeded(context: CoroutineContext): Boolean { return !invokeImmediately || Looper.myLooper() != handler.looper } override fun dispatch(context: CoroutineContext, block: Runnable) { handler.post(block) } } 18 HandlerDispatcher > MainCoroutineDispatcher >> CoroutineDispatcher
  14. Dispatchers(3/3) Android's UI internal class HandlerContext private constructor( private val

    handler: Handler, private val name: String?, private val invokeImmediately: Boolean ) : HandlerDispatcher(), Delay { public constructor( handler: Handler, name: String? = null ) : this(handler, name, false) ... } HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main") 19
  15. CoroutineDispatcher public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { public

    open fun isDispatchNeeded(context: CoroutineContext): Boolean = true public abstract fun dispatch(context: CoroutineContext, block: Runnable) public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation) } 20
  16. ContinuationInterceptor public interface ContinuationInterceptor : CoroutineContext.Element { companion object Key

    : CoroutineContext.Key<ContinuationInterceptor> /** * Returns continuation that wraps the original [continuation], thus intercepting all resumptions. */ public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> } 21
  17. CoroutineDispatcher public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { public

    open fun isDispatchNeeded(context: CoroutineContext): Boolean = true public abstract fun dispatch(context: CoroutineContext, block: Runnable) public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation) } 22
  18. DispatchedContinuation internal class DispatchedContinuation<in T>( @JvmField val dispatcher: CoroutineDispatcher, @JvmField

    val continuation: Continuation<T> ) : DispatchedTask<T>(MODE_ATOMIC_DEFAULT), CoroutineStackFrame, Continuation<T> by continuation { ... } DispatchedContinuation = CoroutineDispatcher + Continuation<T> 23
  19. DispatchedContinuation internal class DispatchedContinuation<in T>( @JvmField val dispatcher: CoroutineDispatcher, @JvmField

    val continuation: Continuation<T> ) : DispatchedTask<T>(MODE_ATOMIC_DEFAULT), CoroutineStackFrame, Continuation<T> by continuation { override fun resumeWith(result: Result<T>) { val context = continuation.context if (dispatcher.isDispatchNeeded(context)) { dispatcher.dispatch(context, this) } else { executeUnconfined() } } } 24
  20. DispatchedContinuation internal class DispatchedContinuation<in T>( @JvmField val dispatcher: CoroutineDispatcher, @JvmField

    val continuation: Continuation<T> ) : DispatchedTask<T>(MODE_ATOMIC_DEFAULT), CoroutineStackFrame, Continuation<T> by continuation { override fun resumeWith(result: Result<T>) { val context = continuation.context if (dispatcher.isDispatchNeeded(context)) { dispatcher.dispatch(context, this) } else { executeUnconfined() } } } CoroutineDispatcher.kt public abstract fun dispatch(context: CoroutineContext, block: Runnable) 25
  21. DispatchedTask DispatchedTask > SchedulerTask > Task > Runnable public final

    override fun run() { try { val continuation = delegate.continuation val context = continuation.context withCoroutineContext(context) { val exception = getExceptionalResult(state) val job = if (resumeMode.isCancellableMode) context[Job] else null if (exception == null && job != null && !job.isActive) { val cause = job.getCancellationException() continuation.resumeWithStackTrace(cause) } else { if (exception != null) continuation.resumeWithException(exception) else continuation.resume(getSuccessfulResult(state)) } } } catch (e: Throwable) { ... 26
  22. DispatchedTask DispatchedTask > SchedulerTask > Task > Runnable public final

    override fun run() { try { val continuation = delegate.continuation val context = continuation.context withCoroutineContext(context) { val exception = getExceptionalResult(state) val job = if (resumeMode.isCancellableMode) context[Job] else null if (exception == null && job != null && !job.isActive) { val cause = job.getCancellationException() continuation.resumeWithStackTrace(cause) } else { if (exception != null) continuation.resumeWithException(exception) else continuation.resume(getSuccessfulResult(state)) } } } catch (e: Throwable) { ... 27
  23. DispatchedTask DispatchedTask > SchedulerTask > Task > Runnable public final

    override fun run() { try { val continuation = delegate.continuation val context = continuation.context withCoroutineContext(context) { val exception = getExceptionalResult(state) val job = if (resumeMode.isCancellableMode) context[Job] else null if (exception == null && job != null && !job.isActive) { val cause = job.getCancellationException() continuation.resumeWithStackTrace(cause) } else { if (exception != null) continuation.resumeWithException(exception) else continuation.resume(getSuccessfulResult(state)) } } } catch (e: Throwable) { ... 28
  24. Recap 1. ContinuationInterceptor intercepts Continuation and wraps into a DispatchedContinuation

    2. CoroutineDispatcher decides whether to dispatch or not 3. If yes, CoroutineDispatcher uses Executor to execute DispatchedTask, which is a Runnable 4. DispatchedTask set correct CoroutineContext and invokes resume() or resumeWithException() 29
  25. handleCoroutineException() 31 public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) { try

    { context[CoroutineExceptionHandler]?.let { it.handleException(context, exception) Return } } catch (t: Throwable) { handleCoroutineExceptionImpl(context, handlerException(exception, t)) Return } handleCoroutineExceptionImpl(context, exception) }
  26. handleCoroutineException() 32 public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) { try

    { context[CoroutineExceptionHandler]?.let { it.handleException(context, exception) Return } } catch (t: Throwable) { handleCoroutineExceptionImpl(context, handlerException(exception, t)) Return } handleCoroutineExceptionImpl(context, exception) }
  27. handleCoroutineException() 33 public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) { try

    { context[CoroutineExceptionHandler]?.let { it.handleException(context, exception) Return } } catch (t: Throwable) { handleCoroutineExceptionImpl(context, handlerException(exception, t)) Return } handleCoroutineExceptionImpl(context, exception) }
  28. handleCoroutineException() 34 public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) { try

    { context[CoroutineExceptionHandler]?.let { it.handleException(context, exception) Return } } catch (t: Throwable) { handleCoroutineExceptionImpl(context, handlerException(exception, t)) Return } handleCoroutineExceptionImpl(context, exception) }
  29. handlerException() internal fun handlerException(originalException: Throwable, thrownException: Throwable): Throwable { if

    (originalException === thrownException) return originalException return RuntimeException("Exception while trying to handle coroutine exception", thrownException).apply { addSuppressedThrowable(originalException) } } 35
  30. handleCoroutineException() 36 public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) { try

    { context[CoroutineExceptionHandler]?.let { it.handleException(context, exception) Return } } catch (t: Throwable) { handleCoroutineExceptionImpl(context, handlerException(exception, t)) Return } handleCoroutineExceptionImpl(context, exception) }
  31. handleCoroutineException() 37 public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) { try

    { context[CoroutineExceptionHandler]?.let { it.handleException(context, exception) Return } } catch (t: Throwable) { handleCoroutineExceptionImpl(context, handlerException(exception, t)) Return } handleCoroutineExceptionImpl(context, exception) }
  32. handleCoroutineExceptionImpl() internal expect fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) “Using the

    keyword expect you can define an API to be implemented on each platform.” “The implementation of an expected declaration is marked with the keyword actual.” 38
  33. handleCoroutineExceptionImpl() JVM private val handlers: List<CoroutineExceptionHandler> = ServiceLoader.load( CoroutineExceptionHandler::class.java, CoroutineExceptionHandler::class.java.classLoader

    ).iterator().asSequence().toList() internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) { for (handler in handlers) { try { handler.handleException(context, exception) } catch (t: Throwable) val currentThread = Thread.currentThread() currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t)) } } val currentThread = Thread.currentThread() currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception) } 39
  34. handleCoroutineExceptionImpl() JVM private val handlers: List<CoroutineExceptionHandler> = ServiceLoader.load( CoroutineExceptionHandler::class.java, CoroutineExceptionHandler::class.java.classLoader

    ).iterator().asSequence().toList() internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) { for (handler in handlers) { try { handler.handleException(context, exception) } catch (t: Throwable) val currentThread = Thread.currentThread() currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t)) } } val currentThread = Thread.currentThread() currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception) } 40
  35. handleCoroutineExceptionImpl() JVM private val handlers: List<CoroutineExceptionHandler> = ServiceLoader.load( CoroutineExceptionHandler::class.java, CoroutineExceptionHandler::class.java.classLoader

    ).iterator().asSequence().toList() internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) { for (handler in handlers) { try { handler.handleException(context, exception) } catch (t: Throwable) val currentThread = Thread.currentThread() currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t)) } } val currentThread = Thread.currentThread() currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception) } 41
  36. handleCoroutineExceptionImpl() JVM private val handlers: List<CoroutineExceptionHandler> = ServiceLoader.load( CoroutineExceptionHandler::class.java, CoroutineExceptionHandler::class.java.classLoader

    ).iterator().asSequence().toList() internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) { for (handler in handlers) { try { handler.handleException(context, exception) } catch (t: Throwable) val currentThread = Thread.currentThread() currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t)) } } val currentThread = Thread.currentThread() currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception) } 42
  37. Reference The suspend modifier — Under the hood - Android

    Developers Coroutines: First things first - Android Developers 45
  38. DispatchedContinuation internal class DispatchedContinuation<in T>( @JvmField val dispatcher: CoroutineDispatcher, @JvmField

    val continuation: Continuation<T> ) : DispatchedTask<T>(MODE_ATOMIC_DEFAULT), CoroutineStackFrame, Continuation<T> by continuation { override fun resumeWith(result: Result<T>) { val context = continuation.context val state = result.toState() if (dispatcher.isDispatchNeeded(context)) { _state = state resumeMode = MODE_ATOMIC_DEFAULT dispatcher.dispatch(context, this) } else { executeUnconfined(state, MODE_ATOMIC_DEFAULT) { withCoroutineContext(this.context, countOrElement) { continuation.resumeWith(result) } 47
  39. DispatchedTask DispatchedTask > SchedulerTask > Task > Runnable public final

    override fun run() { try { val delegate = delegate as DispatchedContinuation<T> val continuation = delegate.continuation val context = continuation.context val state = takeState() withCoroutineContext(context, delegate.countOrElement) { val exception = getExceptionalResult(state) val job = if (resumeMode.isCancellableMode) context[Job] else null if (exception == null && job != null && !job.isActive) { val cause = job.getCancellationException() cancelResult(state, cause) continuation.resumeWithStackTrace(cause) } else { if (exception != null) continuation.resumeWithException(exception) 48