Diving Deep Into the Coroutines API

Diving Deep Into the Coroutines API

May 2019 Diving deep into the Coroutines API Filip Babić About me • Android developer @Five • Android Author and Tech editor for RayWenderlich • Speaking, writing, teaching… The flow • Brief history lesson of async. programming • Introducing coroutines & suspending functions • The inner works of the Coroutines API • Writing quality concurrency code Kotlin Coroutines (Black magic) Kotlin Coroutines (Black magic) Coroutines Coroutines Coroutine Builders Coroutines Coroutine Builders Suspend Functions Coroutines Coroutine Builders Suspend Functions Continuation Suspension Points CoroutineScope Coroutines Coroutine Builders Suspend Functions Continuation Suspension Points CoroutineContext CoroutineScope Coroutines Coroutine Builders Suspend Functions Continuation Suspension Points Well, that’s a lot :] Coroutine builders Launch launch { println("This is a coroutine") } Launch public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job Launch public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job Launch public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job Launch public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job Launch public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job Job Job • Cancellable piece of work • Has lifecycle states • Parent-child job relations Job Start New Started Job Start Complete New Started Completing Job Start Complete Finish New Started Completing Completed Job Start Complete Wait children Finish New Started Completing Completed Job Start Complete Wait children Finish New Started Completing Completed Cancel/Fail Cancelling Job Start Complete Wait children Finish New Started Completing Completed Cancel/Fail Finish Cancelling Cancelled CoroutineScope CoroutineScope launch { println("This is a coroutine") } CoroutineScope with(GlobalScope) { launch { println("This is a coroutine") } } CoroutineScope abstract class BasePresenterImpl<View : BaseView> : BasePresenter<View>, CoroutineScope { private lateinit var view: View protected var parentJob = Job() override fun setView(view: View) { this.view = view } fun onDestroy() = cancel() override val coroutineContext: CoroutineContext get() = Dispatchers.Default + parentJob } CoroutineScope // Somewhere in the presenter launch { println("This is a coroutine") } CoroutineContext CoroutineContext • A set of CoroutineContext elements • Each element dictates one important piece of the puzzle • Lifecyle, Threading, Exception handling CoroutineContext override val coroutineContext: CoroutineContext get() = Dispatchers.Default + parentJob CoroutineContext /** * Persistent context for the coroutine. It is an indexed set of [Element] instances. * An indexed set is a mix between a set and a map. * Every element in this set has a unique [Key]. Keys are compared _by reference_. */ @SinceKotlin("1.3") public interface CoroutineContext { /** * Returns the element with the given [key] from this context or `null`. * Keys are compared _by reference_, that is to get an element from the context the reference to its actual key * object must be presented to this function. */ public operator fun <E : Element> get(key: Key<E>): E? .. Where to find Contexts • Jobs implement the Context interface • ContinuationInterceptor (revolves around threading) • CoroutineExceptionHandler (pretty self-explanatory) Suspension functions Suspension functions • Functions which don’t have to be executed linearly • Can be paused and resumed at any point in time, as many times as needed • Rely on continuations • Suspend modifier CoroutineContext fun printSomeData(data: Any) { println(data) } public final class TestKt { public static final void printSomeData(@NotNull Object data) { Intrinsics.checkParameterIsNotNull(data, "data"); System.out.println(data); } } CoroutineContext suspend fun printSomeData(data: Any) { println(data) } public final class TestKt { @Nullable public static final Object printSomeData(@NotNull Object data, @NotNull Continuation var1) { System.out.println(data); return Unit.INSTANCE; } } CoroutineContext suspend fun printSomeData(data: Any) { delay(100) println(data) } Object $continuation; label28: { if (var1 instanceof <undefinedtype>) { $continuation = (<undefinedtype>)var1; if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) { ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE; break label28; } } $continuation = new ContinuationImpl(var1) { // $FF: synthetic field Object result; int label; Object L$0; @Nullable public final Object invokeSuspend(@NotNull Object result) { this.result = result; this.label |= Integer.MIN_VALUE; return TestKt.printSomeData((Object)null, this); } }; } Object var2 = ((<undefinedtype>)$continuation).result; Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch(((<undefinedtype>)$continuation).label) { case 0: if (var2 instanceof Failure) { throw ((Failure)var2).exception; } ((<undefinedtype>)$continuation).L$0 = data; ((<undefinedtype>)$continuation).label = 1; if (DelayKt.delay(100L, (Continuation)$continuation) == var4) { return var4; } break; case 1: data = ((<undefinedtype>)$continuation).L$0; if (var2 instanceof Failure) { throw ((Failure)var2).exception; } break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } … System.out.println(data); return Unit.INSTANCE; CoroutineContext suspend fun printSomeData(data: Any) { delay(100) println(data) val something = GlobalScope.async { "" } something.await() } So why does this happen? Suspension points Suspension points Suspension points Continuations Continuations • Low level callbacks, which devise and manipulate the execution flow • The system already has continuations implemented • Hold the program state main The process continuation function call getUser main continuation main main process continuation The process continuation suspend con. suspend apiCall getUser con. main con. getUser getUser main process con. main main The process continuation Continuations • Can be cascade -> exceptions, returns • Do not create multiple stack entries • Single stack entry, multiple execution flows Interceptors and handlers ContinuationInterceptor • Handle the internal threading of coroutines • Dispatchers class - Main, IO, Unconfined, Default • Really just a relay for execution flows CoroutineExceptionHandler GlobalScope.launch(Dispatchers.IO) { val result = getExpensiveResult() launch(Dispatchers.Main) { updateUi(result) } } CoroutineExceptionHandler • Handle exceptions within coroutines! • Provide you with the context, so you can restart a coroutine, or create a new one CoroutineExceptionHandler fun main(args: Array<String>) { GlobalScope.launch(context = handler) { throw IllegalArgumentException() } while (true) { } } val handler = CoroutineExceptionHandler { coroutineContext, throwable -> println(coroutineContext) if (throwable is IllegalArgumentException) { println("R.I.P. coroutine") } } Let’s go back to the hands on concepts How to share values • Shared data -> easiest, most volatile • Queues, polling mechanisms -> cool, but can be complex • Futures, promises -> really good, safe, cheap but they can scale bad async/await async/await • Provide an asynchronous construct, which can return values • Make the syntax sequential, and understandable • Rely on coroutines async/await with(GlobalScope) { launch { val expensiveResult = async { getExpensiveResult() } } } async/await launch { val expensiveResultDeferred = async { getExpensiveResult() } val actualResult = expensiveResultDeferred.await() println(actualResult) } } async/await launch { val expensiveResultDeferred = async { getExpensiveResult() } val userDeferred = async { getUser() } printResults(expensiveResultDeferred.await(), userDeferred.await()) } What about safety? Safety val launch = GlobalScope.launch { val result = async { getExpensiveResult() } println(result.await()) } Thread.sleep(50) launch.cancel() Safety fun getExpensiveResult(): Int { var someCondition = false while (true) { //do something println("Running") if (someCondition) { break } } return 100 } Structured and explicit code • Write clear and expressive concurrency code • Rely on isActive parent flags, and finite CoroutineScopes • You don’t have to know everything Sum it up • Coroutines use thread pools, and smart low level callbacks • They do not block threads, as they can be suspended, and navigated with continuations • Using a set of context elements, you can decorate coroutines • Coroutines are very safe and clean, but you can still write crappy code Should I use coroutines? Resources • The project: https://github.com/filbabic/CoroutinesExpoTutorialWorkshop • Kotlin Coroutines by Tutorials Kotlin Coroutines by Tutorials Questions? :].

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    91 Page
  • File Size
    -

Download

Channel Download Status
Express Download Enable

Copyright

We respect the copyrights and intellectual property rights of all users. All uploaded documents are either original works of the uploader or authorized works of the rightful owners.

  • Not to be reproduced or distributed without explicit permission.
  • Not used for commercial purposes outside of approved use cases.
  • Not used to infringe on the rights of the original creators.
  • If you believe any content infringes your copyright, please contact us immediately.

Support

For help with questions, suggestions, or problems, please contact us