Slide 1

Slide 1 text

Kotlin Coroutines Wolfram Rittmeyer

Slide 2

Slide 2 text

What are Coroutines @RittmeyerW

Slide 3

Slide 3 text

“Essentially, coroutines are light-weight threads” @RittmeyerW https://kotlinlang.org/docs/reference/coroutines/basics.html

Slide 4

Slide 4 text

Why concurrency? @RittmeyerW

Slide 5

Slide 5 text

Why concurrency? • Support multiple clients (e.g. server) • React to user input while doing other tasks (UI) • Faster process results (fork/join) • And so on @RittmeyerW

Slide 6

Slide 6 text

Concurrency != Threads • Concurrency possible without threads • See JS which runs (mostly) on one thread • For Java think of NIO / NIO.2 • Really needed only for heavy computation @RittmeyerW

Slide 7

Slide 7 text

Concurrency approaches • Communication via shared state • Callbacks • Futures / Promises • Reactive Extensions • Coroutines @RittmeyerW

Slide 8

Slide 8 text

Blocking solution private fun firstTask(): String { log("about to create the task...") Thread.sleep(500) log("task is created...") return "The answer to life, universe and everything" } @RittmeyerW

Slide 9

Slide 9 text

Blocking solution fun runTasks() { log("blocking solution") val task = firstTask() val result = secondTask(task) log(result) } @RittmeyerW

Slide 10

Slide 10 text

Solution with callbacks private fun firstTask(callback: (String) -> Unit) { log("about to create the task...") thread { Thread.sleep(500) log("task is created...") callback("The answer to life, universe and everything") } } @RittmeyerW

Slide 11

Slide 11 text

Solution with callbacks fun runTasks() { log("using callbacks") firstTask { task -> secondTask(task) { result -> log(result) } } } @RittmeyerW

Slide 12

Slide 12 text

Solution with futures private fun firstTask(): CompletableFuture { log("about to create the task...") val future = CompletableFuture() thread { Thread.sleep(500) log("task is created...") future.complete("The answer to life, universe and everything") } return future } @RittmeyerW

Slide 13

Slide 13 text

Solution with futures fun runTasks() { firstTask() .thenCompose { task -> secondTask(task) } .thenAccept { result -> log(result) } } @RittmeyerW

Slide 14

Slide 14 text

Solution with coroutines private suspend fun firstTask(): String { log("about to create the task...") delay(500) log("task is created...") return "The answer to life, universe and everything" } @RittmeyerW

Slide 15

Slide 15 text

Solution with coroutines fun runTasks() = runBlocking { launch { val firstResult = firstTask() val secondResult = secondTask(firstResult) log(secondResult) } } @RittmeyerW

Slide 16

Slide 16 text

Coroutines concepts @RittmeyerW

Slide 17

Slide 17 text

Suspending Functions • Special modifier suspend • Contains suspension points • That’s where execution pauses • And resumes after the work is done – Details later on :-) @RittmeyerW

Slide 18

Slide 18 text

Suspending Functions suspend fun createTask(): String { log("about to create the task...") delay(500) log("task is created...") return "The answer to life, universe and everything" } @RittmeyerW

Slide 19

Slide 19 text

Suspending Functions suspend fun createTask(): String { log("about to create the task...") delay(500) log("task is created...") return "The answer to life, universe and everything" } @RittmeyerW

Slide 20

Slide 20 text

Suspending Functions fun createTask(): String { log("about to create the task...") delay(500) log("task is created...") return "The answer to life, universe and everything" } @RittmeyerW

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

Coroutine Builders • Creates coroutine • Needed to call suspending functions • Expects a suspending lambda • Standard builders: – runBlocking, launch and async @RittmeyerW

Slide 24

Slide 24 text

Coroutine Builders - runBlocking fun main() { runBlocking { // do some stuff suspendingly: delay(500) println("after delay") // do something else; suspendingly or not } println("after runBlocking will be printed") } @RittmeyerW

Slide 25

Slide 25 text

Coroutine Builders - launch val job = launch { val firstResult = firstTask() val secondResult = secondTask(firstResult) log(secondResult) } // job.join() @RittmeyerW

Slide 26

Slide 26 text

Coroutine Builders - async suspend fun asyncSample() = coroutineScope { val deferred1 = async { someTaskAsync() } val deferred2 = async { someOtherTaskAsync() } deferred1 == deferred2 } @RittmeyerW

Slide 27

Slide 27 text

Coroutine Scopes • Builders are extensions of it – With the exception of runBlocking • Define the scope for coroutines @RittmeyerW

Slide 28

Slide 28 text

Coroutine Scopes • Should match lifecycle of your objects – On Android: Activity/Fragment – On Backend: One request • GlobalScope should be used sparingly @RittmeyerW

Slide 29

Slide 29 text

Coroutine Scopes public interface CoroutineScope { public val coroutineContext: CoroutineContext } @RittmeyerW

Slide 30

Slide 30 text

Coroutine Scopes class SomeActivity : AppCompatActivity(), CoroutineScope { lateinit var job: Job override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) job = Job() } // … onDestroy() on next slide } @RittmeyerW

Slide 31

Slide 31 text

Coroutine Scopes class SomeActivity : AppCompatActivity() { private val job = Job() private var uiScope = CoroutineScope(job + Dispatchers.Main) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) uiScope.launch { // call suspending functions } } // ... onDestroy() on next slide } @RittmeyerW

Slide 32

Slide 32 text

Coroutine Scopes class MainActivity : AppCompatActivity() { private val job = Job() private var uiScope = CoroutineScope(job + Dispatchers.Main) // ... onCreate() on previous slide override fun onDestroy() { super.onDestroy() job.cancel() } } @RittmeyerW

Slide 33

Slide 33 text

Jobs • Provide access to state of coroutine • Provide possibility to cancel • Arranged in parent child hierarchies • Can be joined to other jobs @RittmeyerW

Slide 34

Slide 34 text

Dispatcher • Define the threading strategy • For most use cases predefined ones: – Default – Main / Main.immediate – Unconfined – IO @RittmeyerW

Slide 35

Slide 35 text

Dispatcher • Use with builders – launch(Dispatchers.IO) { … } • Switch when needed – withContext(Dispatchers.IO) { … } • newSingleThreadContext("specialThread") @RittmeyerW

Slide 36

Slide 36 text

Cancelling coroutines @RittmeyerW

Slide 37

Slide 37 text

Cancelling coroutines • Cancelling one job cancels all its childrens • For time based cancellation use – withTimeout(delayInMs) – withTimeoutOrNull(delayInMs) @RittmeyerW

Slide 38

Slide 38 text

Cooperative Cancellation • Suspending functions have to support cancellation • Standard library functions support it • But others? Your own? – Be careful! @RittmeyerW

Slide 39

Slide 39 text

Error handling @RittmeyerW

Slide 40

Slide 40 text

Depends on the Coroutine builder • launch • async @RittmeyerW

Slide 41

Slide 41 text

Depends on scope • If needed: Use different scope • Consider using SupervisorJob – Children fail independently @RittmeyerW

Slide 42

Slide 42 text

ExceptionHandlers val exceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { ctx, exception -> println("exception in handler: $exception - $ctx") exception.printStackTrace() } // ... val scope = CoroutineScope( Dispatchers.Unconfined + job + exceptionHandler ) @RittmeyerW

Slide 43

Slide 43 text

Testing @RittmeyerW

Slide 44

Slide 44 text

Replace Dispatchers • Inject dispatchers (IO / Main) • Replace them in tests with Unconfined @RittmeyerW

Slide 45

Slide 45 text

Use KotlinTest • Spec methods are suspending • Thus no need for runBlocking • Or GlobalScope.async { … } @RittmeyerW

Slide 46

Slide 46 text

And / Or use mockk • Great support for mocking with coroutines • coEvery { definition } returns someObject • coVerify someObject shouldBe expected @RittmeyerW

Slide 47

Slide 47 text

Interoperability @RittmeyerW

Slide 48

Slide 48 text

Coroutines and Java • Compiler cannot manipulate Java code • Thus no direct coroutines usage from Java • Interoperability of shared concepts @RittmeyerW

Slide 49

Slide 49 text

Integrations @RittmeyerW

Slide 50

Slide 50 text

Coroutines and futures • Integrates with CompletableFuture • Only JDK8 API (Android: SDK 24) – CompletionStage.await() – CompletionStage.asDeferred() – Deferred.asCompletableFuture() – future CoroutineBuilder @RittmeyerW

Slide 51

Slide 51 text

Use futures within coroutines • Easily convert existing futures val res = someFuture().await() log(res) @RittmeyerW

Slide 52

Slide 52 text

Convert coroutines to futures • Easily create a future fun asFuture(): CompletableFuture = future { val firstResult = firstTask() val secondResult = secondTask(firstResult) secondResult } @RittmeyerW

Slide 53

Slide 53 text

Wrap a custom API fun startApiWithCallback(): Deferred { val deferred = CompletableDeferred() someApiWithCallback { result -> if (deferred.isActive) { deferred.complete(result) } } return deferred } @RittmeyerW

Slide 54

Slide 54 text

Wrap a custom API suspend fun startApiWithCallback(): String = suspendCancellableCoroutine { continuation -> continuation.invokeOnCancellation { // cancel api call... } println("starting with continuation...") callApiWithContinuation(continuation) } @RittmeyerW

Slide 55

Slide 55 text

Wrap a custom API private fun callApiWithContinuation( continuation: CancellableContinuation) { someApiWithCallback { result -> if (continuation.isActive) { when (result) { is Result.Success -> continuation.resume(result.success) is Result.Error -> continuation.resumeWithException(result.error) } } } } @RittmeyerW

Slide 56

Slide 56 text

Implementation details @RittmeyerW

Slide 57

Slide 57 text

Continuations • Expect a result after the suspension ends • Are heavily used internally @RittmeyerW

Slide 58

Slide 58 text

Continuations public interface Continuation { public val context: CoroutineContext public fun resumeWith(result: Result) } public inline fun Continuation.resume(value: T): Unit = resumeWith(Result.success(value)) public inline fun Continuation.resumeWithException(exception: Throwable): Unit = resumeWith(Result.failure(exception)) @RittmeyerW

Slide 59

Slide 59 text

Continuations public interface Continuation { public val context: CoroutineContext public fun resumeWith(result: Result) } public inline fun Continuation.resume(value: T): Unit = resumeWith(Result.success(value)) public inline fun Continuation.resumeWithException(exception: Throwable): Unit = resumeWith(Result.failure(exception)) @RittmeyerW

Slide 60

Slide 60 text

Base code @RittmeyerW suspend fun sampleFunction() { println("do something") delay(1_000) println("done with something") }

Slide 61

Slide 61 text

Injection of function argument • Compiler modifies signature: – Additional function argument ● of type Continuation – Different return type ● It’s now always Any (Object) @RittmeyerW

Slide 62

Slide 62 text

Injection of function argument @RittmeyerW public final Object sampleFunction(@NotNull Continuation var1) { println("do something") delay(1_000) // suspension point println("done with something") }

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

State machine • State machine used for task switching • Implemented by compiler @RittmeyerW

Slide 65

Slide 65 text

Sample code continued @RittmeyerW public final Object sampleFunction(@NotNull Continuation var1) { println("do something") delay(1_000) // suspension point println("done with something") }

Slide 66

Slide 66 text

Continuation object of sample @RittmeyerW $continuation = new ContinuationImpl(var1) { Object result; int label; Object L$0; @Nullable public final Object invokeSuspend(@NotNull Object result) { this.result = result; this.label |= Integer.MIN_VALUE; return SourceSampleSuspendingFunction.this.sampleFunction(this); } };

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

Simplified sample code @RittmeyerW fun sampleFunction(continuation: Continuation) : Any { val myContinuation = continuation as $continuation when (myContinuation.label) { // ... } println("done with something") return Unit }

Slide 70

Slide 70 text

Simplified sample code @RittmeyerW when (myContinuation.label) { 0 -> { println("do something") val res = delay(1_000, continuation) if (res == COROUTINE_SUSPENDED) { myContinuation.label = 1 return COROUTINE_SUSPENDED } } 1 -> { // error handling omitted for brevity // proper return values would be stored } else -> throw IllegalStateException("something went wrong") }

Slide 71

Slide 71 text

Cooperative multitasking • Mostly implemented by the compiler • Coroutines use cooperative multitasking • Be careful with blocking threads @RittmeyerW

Slide 72

Slide 72 text

Q & A Slide Design by free-powerpoint-templates.com @RittmeyerW