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

Coroutines Made Easy

Ivan
January 18, 2020

Coroutines Made Easy

I reference the content of Kotlinconf 2019 and share some of my thoughts about Coroutines.

Ivan

January 18, 2020
Tweet

More Decks by Ivan

Other Decks in Technology

Transcript

  1. Why Coroutines? • Coroutines ◦ Light-weight threads • Threads are

    expensive ◦ Occupy 1-2 mb ◦ Require Thread pools to manage 3
  2. Coroutine Builders • runBlocking ◦ Bridge between regular and suspend

    functions • launch ◦ Fire and forget • async ◦ Expect result 4
  3. CoroutineScope • Keep track of coroutines • Ability to cancel

    ongoing work • Notified when a failure happens val scope = CoroutineScope(Job()) val job = scope.launch { ... } 5
  4. How to Debug Coroutines? • Intellij ◦ jvmArgs ‘-ea’ •

    Android ◦ • Debug Agent 8 System.setProperty("kotlinx.coroutines.debug", "on") https://proandroiddev.com/android-coroutine-recipes-33467a4302e9 D/main @coroutine#587 [stopCapturing] D/DefaultDispatcher-worker-1 @coroutine#589 [saveImageToGallery]
  5. Cancellation by CoroutineScope val scope = CoroutineScope(Job()) val job =

    scope.launch { ... } val job2 = scope.launch { ... } scope.cancel() 10
  6. Scopes in Android Active Cancelling Cancelled viewModelScope lifecycleScope .cancel() X

    https://android.jlelse.eu/coroutine-in-android-working-with-lifecycle-fc9c1a31e5f3 11
  7. val startTime = System.currentTimeMillis() val job = launch (Dispatchers.Default) {

    var nextPrintTime = startTime var i = 0 while (i < 5) { if (...) { println("Hello ${i++}") nextPrintTime += 500L } } } delay(1000L) println("Cancel:") job.cancel() println("Done!") Hello 0 Hello 1 Hello 2 Cancel! Done! Hello 3 Hello 4 12
  8. val startTime = System.currentTimeMillis() val job = launch (Dispatchers.Default) {

    var nextPrintTime = startTime var i = 0 while (i < 5 && isActive) { if (...) { println("Hello ${i++}") nextPrintTime += 500L } } } delay(1000L) println("Cancel:") job.cancel() println("Done!") Hello 0 Hello 1 Hello 2 Cancel! Done! 14
  9. val startTime = System.currentTimeMillis() val job = launch (Dispatchers.Default) {

    var nextPrintTime = startTime var i = 0 while (i < 5) { ensureActive() if (...) { println("Hello ${i++}") nextPrintTime += 500L } } } delay(1000L) println("Cancel:") job.cancel() println("Done!") Hello 0 Hello 1 Hello 2 Cancel! Done! 15
  10. val startTime = System.currentTimeMillis() val job = launch (Dispatchers.Default) {

    var nextPrintTime = startTime var i = 0 while (i < 5) { yeild() if (...) { println("Hello ${i++}") nextPrintTime += 500L } } } delay(1000L) println("Cancel:") job.cancel() println("Done!") Hello 0 Hello 1 Hello 2 Cancel! Done! 16
  11. Cooperative Cancellation • isActive ◦ Do an action before finishing

    the coroutine • ensureActive() ◦ Instantaneously stop work • yield() ◦ CPU heavy computation that may exhaust thread-pool 17
  12. Cancellable Suspend Functions val job = launch { workOne() yield()

    // or ensureActive() workTwo() } suspend fun workOne() = withContext(Dispatcher.IO) { otherWork() yield() // or ensureOtherWork() someOtherWork() } 18
  13. val job = launch(Dispatchers.Default) { repeat(5) { println("Hello $it") delay(500)

    } } delay(1000) job.join() println("Cancel!") job.cancel() println("Done!") Hello 0 Hello 1 Hello 2 Hello 3 Hello 4 Cancel! Done! 20
  14. val job = launch(Dispatchers.Default) { repeat(5) { println("Hello $it") delay(500)

    } } delay(1000) println("Cancel!") job.cancel() job.join() println("Done!") Hello 0 Hello 1 Cancel! Done! 21
  15. val job = async(Dispatchers.Default) { repeat(5) { println("Hello $it") delay(500)

    } } delay(1000) val result = job.await() println("Cancel!") job.cancel() println("Done!") Hello 0 Hello 1 Hello 2 Hello 3 Hello 4 Cancel! Done! 22
  16. val job = async(Dispatchers.Default) { repeat(5) { println("Hello $it") delay(500)

    } } delay(1000) println("Cancel!") job.cancel() val result = job.await() println("Done!") Hello 0 Hello 1 Cancel! Done! JobCancellationException: Job was cancelled! 23
  17. Suspend Jobs Until the Job Is Completed • join() ◦

    No action if completed • await() ◦ Throws if completed 24
  18. val job = launch { try { work() } catch

    (e: CancellationException) { println("Work cancelled!") } } delay(1000L) println("Cancel!") job.cancel() println("Done!") Hello 0 Hello 1 Cancel! Work cancelled! Done! 26
  19. val job = launch { try { work() } finally

    { println("Clean up!") delay(1000L) println("Cleanup done!") } } delay(1000L) println("Cancel!") job.cancel() println("Done!") Hello 0 Hello 1 Cancel! Clean up! Done! 27
  20. val job = launch { try { work() } finally

    { println("Clean up!") withContext(NonCancellable) { delay(1000L) println("Cleanup done!") } } } delay(1000L) println("Cancel!") job.cancel() println("Done!") Hello 0 Hello 1 Cancel! Clean up! Cleanup done! Done! 28
  21. Failures in a Scope • The failure of child cancels

    the scope & other children val scop = CoroutineScope(Job()) 32
  22. SupervisorJob • The failure or cancellation of a child does

    not affect other children val scop = CoroutineScope(SupervisorJob()) 33
  23. val scope = CoroutineScope(Job()) scope.launch(SupervisorJob()) { launch { // Child

    1 } launch { // Child 2 } } 38 SupervisorJob Job Child Job 1 Child Job 2 Job
  24. Try/Catch supervisorScope { val deferred = async { codeThatThrowsException() }

    try { deferred.await() } catch (e: Exception) { // Handle it! } } 45
  25. Try/Catch coroutineScope { val deferred = async { codeThatThrowsException() }

    try { deferred.await() } catch (e: Exception) { // This won't be called! } } 46
  26. Use runCatching() scope.launch { val result = runCatching { codeThatThrowsExceptions()

    } if (result.isSuccess) { // Yeah! } else { // No! } } 47
  27. CoroutineExceptionHandler val handler = CoroutineExceptionHandler { _, exception -> println("Caught

    $exception") } val scope = CoroutineScope(Job() + handler) scope.launch { launch { throw Exception("Boom!") } } 48
  28. CoroutineExceptionHandler val handler = CoroutineExceptionHandler { _, exception -> println("Caught

    $exception") } val scope = CoroutineScope(Job()) scope.launch { launch(handler) { throw Exception("Boom!") } } 49
  29. CoroutineExceptionHandler val handler = CoroutineExceptionHandler { _, exception -> println("Caught

    $exception") } val scope = CoroutineScope(Job()) scope.launch(handler) { launch { throw Exception("Boom!") } } 50
  30. Using kotlinx-coroutines-test • Experimental APIs • runBlockingTest() ◦ It executes

    code in one thread ◦ Virtual-time control ◦ Cleanup assertions 52
  31. Testing Tips • Always inject scope or dispatcher • Inject

    main dispatcher ◦ Dispatchers.setMain ◦ Dispatchers.resetMain • Codelab 54
  32. CREDITS: This presentation template was created by Slidesgo, including icons

    by Flaticon, and infographics & images by Freepik. THANKS! Do you have any questions? 55
  33. References 56 • KotlinConf 2019: Coroutines! Gotta catch 'em all!

    by Florina Muntenescu & Manuel Vivo • KotlinConf 2019: Asynchronous Data Streams with Kotlin Flow by Roman Elizarov • KotlinConf 2019: Testing with Coroutines by Sean McQuillan • KotlinConf 2019: Coroutines Case Study - Cleaning Up an Async API by Tom Hanley