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

Asynchronous Kotlin

Asynchronous Kotlin

This presentation introduces coroutines to the viewers.

Chandra Sekhar Nayak

May 07, 2019
Tweet

More Decks by Chandra Sekhar Nayak

Other Decks in Technology

Transcript

  1. Coroutines Coroutines are similar to Threads. Threads Coroutines Preemptively Multitasking

    Cooperatively Multitasking Parallelism Concurrency Not useful in Realtime context Useful in Realtime context
  2. Coroutines Coroutines are similar to Threads. Threads Coroutines Preemptively Multitasking

    Cooperatively Multitasking Parallelism Concurrency Not useful in Realtime context Useful in Realtime context Context Switching needs system call Context Switching doesn't need system call
  3. Coroutines Coroutines are similar to Threads. Threads Coroutines Preemptively Multitasking

    Cooperatively Multitasking Parallelism Concurrency Not useful in Realtime context Useful in Realtime context Context Switching needs system call Context Switching doesn't need system call Synchronization Primitives (Mutexes, Semaphores) are required Mutexes or Semaphores are not required
  4. Hello World public class HelloWorld { public static void main(String[]

    args) throws InterruptedException { new Thread(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("World!!!"); }).start(); System.out.print("Hello "); Thread.sleep(2000L); } }
  5. Hello World public static void main(String[] args) throws InterruptedException {

    new Thread(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("World!!!"); }).start(); System.out.print("Hello "); Thread.sleep(2000L); }
  6. Hello World fun main() { new Thread(() -> { try

    { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("World!!!"); }).start(); System.out.print("Hello "); Thread.sleep(2000L); }
  7. Hello World fun main() { GlobalScope.launch { try { Thread.sleep(1000L);

    } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("World!!!"); } System.out.print("Hello "); Thread.sleep(2000L); }
  8. Hello World public class HelloWorld { public static void main(...)

    { new Thread(() -> { try { Thread.sleep(1000L); } catch (...) { e.printStackTrace(); } S.o.p("World!!!"); }).start(); S.o.p("Hello "); Thread.sleep(2000L); } } fun main() { GlobalScope.launch { delay(1000L) println("World!!!") } print("Hello ") Thread.sleep(2000L) }
  9. No - Thread.sleep( … ), Yes - runBlocking { …

    } fun main() { GlobalScope.launch { delay(1000L) println("World!!!") } print("Hello ") runBlocking { delay(2000L) } }
  10. Idiomatic runBlocking { … } fun main() = runBlocking {

    GlobalScope.launch { delay(1000L) println("World!!!") } print("Hello ") delay(2000L) }
  11. Is delay( … ) a good idea? fun main() =

    runBlocking { val job = GlobalScope.launch { delay(1000L) println("World!!!") } print("Hello ") job.join() }
  12. Scope Builder fun main() = runBlocking { launch { delay(200L)

    println("Task from runBlocking") } coroutineScope { launch { delay(500L) println("Task from nested launch") } delay(100L) println("Task from coroutine scope") } println("Coroutine scope is over") }
  13. 1st Suspend Function fun main() = runBlocking { launch {

    printLowes() } print("Hello ") } fun printLowes() { delay(2000L) println("World!!!") }
  14. 1st Suspend Function fun main() = runBlocking { launch {

    printLowes() } print("Hello ") } suspend fun printLowes() { delay(2000L) println("World!!!") }
  15. Coroutines are Light Weight fun main() = runBlocking { repeat(100_000)

    { launch { delay(1000L) print("Hello World!!!, ") } } }
  16. Global Coroutines are like Daemon Threads fun main() = runBlocking

    { GlobalScope.launch { repeat(1000) { i -> println("Count - $i") delay(500L) } } delay(1300L) }
  17. Cancelling Coroutine Execution fun main() = runBlocking { val job

    = launch { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } delay(1300L) println("Done with waiting...") job.cancel() job.join() println("Job Cancelled") }
  18. Cancellation is Cooperative fun main() = runBlocking { val startTime

    = System.currentTimeMillis() val job = launch(Dispatchers.Default) { var nextPrintTime = startTime var i = 0 while (i < 5) { // computation loop, just wastes CPU // print a message twice a second if (System.currentTimeMillis() >= nextPrintTime) { println("I'm sleeping ${i++} ...") nextPrintTime += 500L } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.") }
  19. Making Computation Code Cancellable fun main() = runBlocking { val

    startTime = System.currentTimeMillis() val job = launch(Dispatchers.Default) { var nextPrintTime = startTime var i = 0 while (isActive) { // cancellable computation loop // print a message twice a second if (System.currentTimeMillis() >= nextPrintTime) { println("I'm sleeping ${i++} ...") nextPrintTime += 500L } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.") }
  20. Closing Resources with finally fun main() = runBlocking { val

    job = launch { try { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } finally { println("I'm running finally") } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.") }
  21. Run non-cancellable Block fun main() = runBlocking { val job

    = launch { try { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } finally { withContext(NonCancellable) { println("I'm running finally") delay(1000L) println("Delayed for 1 sec because I'm non-cancellable") } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.")
  22. Timeout fun main() = runBlocking { withTimeout(1300L) { repeat(1000) {

    i -> println("I'm sleeping $i ...") delay(500L) } } }
  23. Timeout fun main() = runBlocking { try { withTimeout(1300L) {

    repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } } catch (ex: TimeoutCancellationException) { println("Timed out...") } }
  24. Timeout fun main() = runBlocking { val result = withTimeoutOrNull(1300L)

    { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } "Task completed ..." // Gets cancelled before this result } println(result ?: "Timed out ...") }
  25. Sequential by default suspend fun doTaskOne(): Int { delay(1000L) return

    10 } suspend fun doTaskTwo(): Int { delay(1000L) return 20 }
  26. Sequential by default suspend fun doTaskOne(): Int { delay(1000L) return

    10 } suspend fun doTaskTwo(): Int { delay(1000L) return 20 } fun main() = runBlocking { val time = measureTimeMillis { val t1 = doTaskOne() val t2 = doTaskTwo() println("Result - ${t1 + t2}") } println("Time Taken - $time") }
  27. Concurrent using async suspend fun doTaskOne(): Int { delay(1000L) return

    10 } suspend fun doTaskTwo(): Int { delay(1000L) return 20 } fun main() = runBlocking { val time = measureTimeMillis { val t1 = async { doTaskOne() } val t2 = async { doTaskTwo() } println("Result - ${t1.await() + t2.await()}") } println("Time Taken - $time") }
  28. launch vs async 1. launch returns Job 1. async returns

    Deferred They both start a coroutine which runs concurrently with all other coroutines.
  29. launch vs async 1. launch returns Job 2. launch does

    not carry any resulting value 1. async returns Deferred They both start a coroutine which runs concurrently with all other coroutines.
  30. launch vs async 1. launch returns Job 2. launch does

    not carry any resulting value 1. async returns Deferred 2. async carries resulting value They both start a coroutine which runs concurrently with all other coroutines.
  31. Lazily started async suspend fun doTaskOne(): Int { delay(1000L) return

    10 } suspend fun doTaskTwo(): Int { delay(1000L) return 20 } fun main() = runBlocking { val time = measureTimeMillis { val t1 = async(start = CoroutineStart.LAZY) { doTaskOne() } val t2 = async(start = CoroutineStart.LAZY) { doTaskTwo() } t1.start() t2.start() println("Result - ${t1.await() + t2.await()}") } println("Time Taken - $time") }
  32. Lazy async without start suspend fun doTaskOne(): Int { delay(1000L)

    return 10 } suspend fun doTaskTwo(): Int { delay(1000L) return 20 } fun main() = runBlocking { val time = measureTimeMillis { val t1 = async(start = CoroutineStart.LAZY) { doTaskOne() } val t2 = async(start = CoroutineStart.LAZY) { doTaskTwo() } println("Result - ${t1.await() + t2.await()}") } println("Time Taken - $time") }
  33. Async-Style Functions fun doTaskOneAsync() = GlobalScope.async { doTaskOne() } fun

    doTaskTwoAsync() = GlobalScope.async { doTaskTwo() }
  34. Async-Style Functions fun doTaskOneAsync() = GlobalScope.async { doTaskOne() } fun

    doTaskTwoAsync() = GlobalScope.async { doTaskTwo() } fun main() { val time = measureTimeMillis { val t1 = doTaskOneAsync() val t2 = doTaskTwoAsync() runBlocking { println("Result - ${t1.await() + t2.await()}") } } println("Time Taken - $time")
  35. Structured Concurrency with async fun main() = runBlocking { val

    time = measureTimeMillis { val t1 = async { doTaskOne() } val t2 = async { doTaskTwo() } println("Result - ${t1.await() + t2.await()}") } println("Time Taken - $time") }
  36. Structured Concurrency with async suspend fun concurrentSum(): Int = coroutineScope

    { val t1 = async { doTaskOne() } val t2 = async { doTaskTwo() } t1.await() + t2.await() }
  37. Structured Concurrency with async suspend fun concurrentSum(): Int = coroutineScope

    { val t1 = async { doTaskOne() } val t2 = async { doTaskTwo() } t1.await() + t2.await() } fun main() = runBlocking { val time = measureTimeMillis { println("Result - ${concurrentSum()}") } println("Time Taken - $time") }
  38. Structured Concurrency when error suspend fun failedConcurrentSum(): Int = coroutineScope

    { val t1 = async { try { delay(Long.MAX_VALUE) 40 } finally { println("First child got cancelled ...") } } val t2 = async<Int> { println("Second child throws exception ...") throw ArithmeticException() } t1.await() + t2.await() }
  39. Structured Concurrency when error ... val t2 = async<Int> {

    println("Second child throws exception ...") throw ArithmeticException() } t1.await() + t2.await() } fun main() = runBlocking<Unit> { try { failedConcurrentSum() } catch (ex: ArithmeticException) { println("Failed with Arithmetic Exception") } }
  40. Coroutine Context 1. All coroutines are run in some context

    called CoroutineContext. 2. CoroutineContext is an interface in implementation. 3. It has various Elements. 4. Major elements are: a. Job - represents a background job b. CoroutineDispatcher - represents which thread the coroutine uses for its execution
  41. Dispatchers and Threads fun main() = runBlocking<Unit> { launch {

    println("main > runBlocking > launch") println("running in - ${Thread.currentThread().name}") } println("main > runBlocking") println("running in - ${Thread.currentThread().name}") } Child coroutine inherits coroutine context from it's parent coroutine. Hence it inherits the dispatcher.
  42. Dispatchers and Threads fun main() = runBlocking<Unit> { launch(Dispatchers.Unconfined) {

    println("main > runBlocking > launch") println("running in - ${Thread.currentThread().name}") } } Dispatcher for child coroutine is not confined. So will work with main thread.
  43. Dispatchers and Threads fun main() = runBlocking<Unit> { launch(Dispatchers.Default) {

    println("main > runBlocking > launch") println("running in - ${Thread.currentThread().name}") } } Default dispatcher. This is also used when coroutine is launched in GlobalScope. This uses a shared pool of background threads.
  44. Dispatchers and Threads fun main() = runBlocking<Unit> { launch(Dispatchers.Main) {

    println("main > runBlocking > launch") println("running in - ${Thread.currentThread().name}") } } Dispatcher for Android's main thread.
  45. Dispatchers and Threads fun main() = runBlocking<Unit> { launch(newSingleThreadContext("MyThread")) {

    println("main > runBlocking > launch") println("running in - ${Thread.currentThread().name}") } } Creates a new dedicated thread for the coroutine to run.
  46. Unconfined vs Confined Dispatcher Unconfined Coroutine Confined Coroutine Not confined

    to work with a single thread. Confined to work with the thread inherited from parent CoroutineContext
  47. Unconfined vs Confined Dispatcher Unconfined Coroutine Confined Coroutine Not confined

    to work with a single thread. Confined to work with the thread inherited from parent CoroutineContext Unconfined coroutine dispatcher starts in the caller thread but until the first suspension. After the first suspension, it resumes in a thread defined by the suspension function which was called.
  48. Unconfined vs Confined Dispatcher fun main() = runBlocking<Unit> { launch(Dispatchers.Unconfined)

    { println("main > runBlocking > Unconfined launch") println("Before Delay - Running in - ${currentThread().name}") delay(500L) println("After Delay - Running in - ${currentThread().name}") } launch { println("main > runBlocking > launch") println("Before Delay - Running in - ${currentThread().name}") delay(1000L) println("After Delay - Running in - ${currentThread().name}") } }
  49. Jumping Between Threads fun main() { newSingleThreadContext("Context1").use { ctx1 ->

    newSingleThreadContext("Context2").use { ctx2 -> runBlocking(ctx1) { println("Started in ${currentThread().name}") withContext(ctx2) { println("Working in ${currentThread().name}") } println("Back to ${currentThread().name}") } } } }
  50. Job in the Context 1. Each coroutine runs in a

    CoroutineScope. 2. CoroutineScope has a CoroutineContext. 3. CoroutineContext has a set of elements including the Job. 4. Job can be fun main() = runBlocking { println("My Job is - ${coroutineContext[Job]}") }
  51. Children of a Coroutine fun main() = runBlocking { val

    request = launch { GlobalScope.launch { println("Job1: Inside GlobalScope") delay(1000L) println("Job1: No impact on cancellation of the request") } launch { delay(100L) println("Job2: Child of request coroutine") delay(1000L) println("Job2: No exec, if parent coroutine is cancelled") } } delay(500L) request.cancel() delay(1000L) println("main: Finished") }
  52. Parent waits until all Children are done fun main() =

    runBlocking { val request = launch { repeat(3) { i -> launch { delay((i + 1) * 200L) println("Coroutine $i is done ...") } } println("Request: I am done ...") } request.join() println("Processing of request is done ...") }
  53. Coroutine Scope w.r.t Android class MainActivity { private val mainScope

    = MainScope() fun onDestroy() { mainScope.cancel() } // to be continued ... }
  54. Coroutine Scope w.r.t Android class MainActivity { private val mainScope

    = MainScope() fun onDestroy() { mainScope.cancel() } // to be continued ... } class MainActivity : CoroutineScope by CoroutineScope(Dispatchers.Default) { // to be continued ... }
  55. Coroutine Scope w.r.t Android class MainActivity : CoroutineScope by CoroutineScope(Dispatchers.Default)

    { // to be continued ... fun doSomething() { // launch ten coroutines, each working for a different time repeat(10) { i -> launch { delay((i + 1) * 200L) // delay 200ms, 400ms, ... etc println("Coroutine $i is done") } } } fun onDestroy() { cancel() } }
  56. Coroutine Scope w.r.t Android fun main() = runBlocking { val

    activity = MainActivity2() activity.doSomething() println("Launched multiple coroutines") delay(500L) println("Destroying Activity") activity.onDestroy() delay(1000L) }
  57. Problem private suspend fun CoroutineScope.massiveRun(action: suspend () -> Unit) {

    val n = 100 // number of coroutines to launch val k = 1000 // times an action is repeated by each coroutine val time = measureTimeMillis { val jobs = List(n) { launch { repeat(k) { action() } } } jobs.forEach { it.join() } } println("Completed ${n * k} actions in $time ms") }
  58. Problem var counter = 0 fun main() = runBlocking {

    GlobalScope.massiveRun { counter++ } println("Counter - $counter") }
  59. Problem and Volatile @Volatile var counter = 0 fun main()

    = runBlocking { GlobalScope.massiveRun { counter++ } println("Counter - $counter") } Volatile guaratee Atomic Reads and Writes operation on a variable. It doesn’t provide atomicity on complex actions (Increment in this case).
  60. Thread Confinement - Fine Grained private val counterContext = newSingleThreadContext("CounterContext")

    private var counter = 0 fun main() = runBlocking { GlobalScope.massiveRun { // run each coroutine with DefaultDispathcer withContext(counterContext) { // but confine each increment to the single-threaded context counter++ } } println("Counter = $counter") }
  61. Thread Confinement - Coarse Grained fun main() = runBlocking {

    CoroutineScope(counterContext).massiveRun { // run each coroutine with DefaultDispathce counter++ } println("Counter = $counter") }
  62. Mutual Exclusion private var counter = 0 private val mutex

    = Mutex() fun main() = runBlocking { GlobalScope.massiveRun { mutex.lock() try { counter++ } finally { mutex.unlock() } } println("Counter = $counter") } Fine Grained