Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Deep dive into Coroutines on JVM @ KotlinConf 2017

2,056 views

Published on

Deep dive into Coroutines on JVM, presented at KotlinConf 2017; video is available https://www.youtube.com/watch?v=YrrUCSi72E8&t=5s

Published in: Technology

Deep dive into Coroutines on JVM @ KotlinConf 2017

  1. 1. elizarov at JetBrains Roman Elizarov Deep dive into Coroutines on JVM
  2. 2. There is no magic
  3. 3. Continuation Passing Style (CPS)
  4. 4. A toy problem fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) } Direct style
  5. 5. Direct style fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }
  6. 6. Direct style fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) } Continuation
  7. 7. Continuation-Passing Style fun postItem(item: Item) { requestToken { token -> val post = createPost(token, item) processPost(post) } } Continuation CPS == Callbacks
  8. 8. Continuation-Passing Style fun postItem(item: Item) { requestToken { token -> createPost(token, item) { post -> processPost(post) } } }
  9. 9. Coroutines Direct Style suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }
  10. 10. How does it work? Behind the scenes
  11. 11. Kotlin suspending functions Kotlin suspend fun createPost(token: Token, item: Item): Post { … }
  12. 12. CPS Transformation Java/JVM suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … }
  13. 13. CPS Transformation callback Java/JVM suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … }
  14. 14. Continuation suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }
  15. 15. Continuation suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }
  16. 16. Continuation suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }
  17. 17. Continuation suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }
  18. 18. Continuation Continuation is a generic callback interface suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }
  19. 19. Direct to CPS
  20. 20. Direct code suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }
  21. 21. Continuations suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) } Initial continuation
  22. 22. Continuations suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) } Continuation
  23. 23. Continuations suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) } Continuation Convert to CPS?
  24. 24. Callbacks? fun postItem(item: Item) { requestToken { token -> createPost(token, item) { post -> processPost(post) } } }
  25. 25. Labels suspend fun postItem(item: Item) { // LABEL 0 val token = requestToken() // LABEL 1 val post = createPost(token, item) // LABEL 2 processPost(post) }
  26. 26. Labels suspend fun postItem(item: Item) { switch (label) { case 0: val token = requestToken() case 1: val post = createPost(token, item) case 2: processPost(post) } }
  27. 27. suspend fun postItem(item: Item) { val sm = object : CoroutineImpl { … } switch (sm.label) { case 0: val token = requestToken() case 1: val post = createPost(token, item) case 2: processPost(post) } } State
  28. 28. fun postItem(item: Item, cont: Continuation) { val sm = object : CoroutineImpl { … } switch (sm.label) { case 0: requestToken(sm) case 1: createPost(token, item, sm) case 2: processPost(post) } } CPS Transform
  29. 29. fun postItem(item: Item, cont: Continuation) { val sm = … switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: createPost(token, item, sm) case 2: processPost(post) } } Save state
  30. 30. fun postItem(item: Item, cont: Continuation) { val sm = object : CoroutineImpl { … } switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: createPost(token, item, sm) case 2: processPost(post) } } Callback State Machine as Continuation
  31. 31. fun postItem(item: Item, cont: Continuation) { val sm = object : CoroutineImpl { fun resume(…) { postItem(null, this) } } switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: createPost(token, item, sm) … } Callback
  32. 32. fun postItem(item: Item, cont: Continuation) { val sm = cont as? ThisSM ?: object : ThisSM { fun resume(…) { postItem(null, this) } } switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: createPost(token, item, sm) … } Callback
  33. 33. fun postItem(item: Item, cont: Continuation) { val sm = … switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: val item = sm.item val token = sm.result as Token sm.label = 2 createPost(token, item, sm) … } Restore state
  34. 34. fun postItem(item: Item, cont: Continuation) { val sm = … switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: val item = sm.item val token = sm.result as Token sm.label = 2 createPost(token, item, sm) … } Continue
  35. 35. State Machine vs Callbacks fun postItem(item: Item) { requestToken { token -> createPost(token, item) { post -> processPost(post) } } } suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }
  36. 36. State Machine vs Callbacks fun postItem(item: Item) { requestToken { token -> createPost(token, item) { post -> processPost(post) } } } suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) } Reuse closure / state object Create new closure
  37. 37. State Machine vs Callbacks suspend fun postItems(items: List<Item>) { for (item in items) { val token = requestToken() val post = createPost(token, item) processPost(post) } } Easy loops and higher-order functions
  38. 38. State Machine vs Callbacks fun postItems(items: List<Item>) { … } suspend fun postItems(items: List<Item>) { for (item in items) { val token = requestToken() val post = createPost(token, item) processPost(post) } } A horrid callback mess Easy loops and higher-order functions
  39. 39. Integration
  40. 40. Zoo of futures on JVM
  41. 41. interface Service { fun createPost(token: Token, item: Item): Call<Post> }
  42. 42. interface Service { fun createPost(token: Token, item: Item): Call<Post> } suspend fun createPost(token: Token, item: Item): Post = serviceInstance.createPost(token, item).await()
  43. 43. suspend fun <T> Call<T>.await(): T { … }
  44. 44. Callbacks everywhere suspend fun <T> Call<T>.await(): T { enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { // todo } override fun onFailure(call: Call<T>, t: Throwable) { // todo } }) }
  45. 45. suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont -> enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { if (response.isSuccessful) cont.resume(response.body()!!) else cont.resumeWithException(ErrorResponse(response)) } override fun onFailure(call: Call<T>, t: Throwable) { cont.resumeWithException(t) } }) }
  46. 46. suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T
  47. 47. suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T Regular function Inspired by call/cc from Scheme
  48. 48. Install callback suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont -> enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { if (response.isSuccessful) cont.resume(response.body()!!) else cont.resumeWithException(ErrorResponse(response)) } override fun onFailure(call: Call<T>, t: Throwable) { cont.resumeWithException(t) } }) }
  49. 49. Install callback suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont -> enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { if (response.isSuccessful) cont.resume(response.body()!!) else cont.resumeWithException(ErrorResponse(response)) } override fun onFailure(call: Call<T>, t: Throwable) { cont.resumeWithException(t) } }) }
  50. 50. Analyze response suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont -> enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { if (response.isSuccessful) cont.resume(response.body()!!) else cont.resumeWithException(ErrorResponse(response)) } override fun onFailure(call: Call<T>, t: Throwable) { cont.resumeWithException(t) } }) }
  51. 51. Analyze response suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont -> enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { if (response.isSuccessful) cont.resume(response.body()!!) else cont.resumeWithException(ErrorResponse(response)) } override fun onFailure(call: Call<T>, t: Throwable) { cont.resumeWithException(t) } }) } That’s all
  52. 52. Out-of-the box integrations kotlinx-coroutines-core jdk8 guava nio reactor rx1 rx2 Contributions are welcome
  53. 53. Coroutine context
  54. 54. What thread it resumes on? suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) } Continuation It depends!
  55. 55. What thread it resumes on? fun postItem(item: Item) { launch(UI) { val token = requestToken() val post = createPost(token, item) processPost(post) } } Continuation
  56. 56. Continuation Interceptor interface ContinuationInterceptor : CoroutineContext.Element { companion object Key : CoroutineContext.Key<ContinuationInterceptor> fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> }
  57. 57. Continuation Interceptor interface ContinuationInterceptor : CoroutineContext.Element { companion object Key : CoroutineContext.Key<ContinuationInterceptor> fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> }
  58. 58. Continuation Interceptor interface ContinuationInterceptor : CoroutineContext.Element { companion object Key : CoroutineContext.Key<ContinuationInterceptor> fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> }
  59. 59. Dispatched continuation class DispatchedContinuation<in T>( val dispatcher: CoroutineDispatcher, val continuation: Continuation<T> ): Continuation<T> by continuation { override fun resume(value: T) { dispatcher.dispatch(context, DispatchTask(…)) } … } Dispatches execution to another thread
  60. 60. Starting coroutines
  61. 61. fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T> Coroutine builder
  62. 62. fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T> A regular function
  63. 63. fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T>
  64. 64. fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T> suspending lambda
  65. 65. fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T> { val future = CompletableFuture<T>() block.startCoroutine(…) return future }
  66. 66. fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T> { val future = CompletableFuture<T>() block.startCoroutine(…) return future }
  67. 67. fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T> { val future = CompletableFuture<T>() block.startCoroutine(completion = object : Continuation<T> { … }) return future }
  68. 68. fun <T> future(…): CompletableFuture<T> { val future = CompletableFuture<T>() block.startCoroutine(completion = object : Continuation<T> { override val context: CoroutineContext get() = context override fun resume(value: T) { future.complete(value) } override fun resumeWithException(exception: Throwable) { future.completeExceptionally(exception) } }) return future }
  69. 69. fun <T> future(…): CompletableFuture<T> { val future = CompletableFuture<T>() block.startCoroutine(completion = object : Continuation<T> { override val context: CoroutineContext get() = context override fun resume(value: T) { future.complete(value) } override fun resumeWithException(exception: Throwable) { future.completeExceptionally(exception) } }) return future } That’s all, folks!
  70. 70. Job cancellation
  71. 71. Launch coroutine builder fun launch( context: CoroutineContext = DefaultDispatcher, block: suspend () -> Unit ): Job { … }
  72. 72. Launching coroutine val job = launch { … }
  73. 73. val job = launch { … } job.join()
  74. 74. val job = launch { … } job.join() job.cancel()
  75. 75. Job interface Job : CoroutineContext.Element { companion object Key : CoroutineContext.Key<Job> … }
  76. 76. Using coroutine context launch { val job = coroutineContext[Job]!! … }
  77. 77. Using coroutine context launch { val job = coroutineContext[Job]!! val interceptor = coroutineContext[CoroutineInterceptor]!! … }
  78. 78. Timeouts launch { withTimeout(10, TimeUnit.SECONDS) { … } }
  79. 79. Cooperative cancellation
  80. 80. Cooperative cancellation launch { while (true) { … } }
  81. 81. Cooperative cancellation launch { while (isActive) { … } }
  82. 82. Cooperative cancellation launch { while (true) { delay(…) … } }
  83. 83. Cancellable suspension suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine { cont -> enqueue(…) }
  84. 84. Cancellable continuation suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine { cont: CancellableContinuation<T> -> enqueue(…) }
  85. 85. Completion handler suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine { cont: CancellableContinuation<T> -> enqueue(…) cont.invokeOnCompletion { this@await.cancel() } }
  86. 86. Completion handler suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine { cont: CancellableContinuation<T> -> enqueue(…) cont.invokeOnCompletion { this@await.cancel() } }
  87. 87. Communicating Sequential Processes (CSP)
  88. 88. Shared Mutable State @stefanobaghino
  89. 89. The choice Shared Mutable State Share by Communicating
  90. 90. Example fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }
  91. 91. Main coroutine fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }
  92. 92. Channel fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }
  93. 93. Launch fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } } Child coroutine
  94. 94. Coroutine body fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } } Sequential code!
  95. 95. Send fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }
  96. 96. Close fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }
  97. 97. Receive for loop fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }
  98. 98. Demo
  99. 99. Actors The other way to look at CSP
  100. 100. The choice Named channels Named coroutines Actor == named coroutine & inbox channel
  101. 101. Example fun main(args: Array<String>) = runBlocking<Unit> { val printer = actor<Int>(coroutineContext) { for (i in channel) { println(i) } } launch(coroutineContext) { repeat(10) { i -> delay(100) printer.send(i) } printer.close() } }
  102. 102. Actor coroutine builder fun main(args: Array<String>) = runBlocking<Unit> { val printer = actor<Int>(coroutineContext) { for (i in channel) { println(i) } } launch(coroutineContext) { repeat(10) { i -> delay(100) printer.send(i) } printer.close() } }
  103. 103. Actor body fun main(args: Array<String>) = runBlocking<Unit> { val printer = actor<Int>(coroutineContext) { for (i in channel) { println(i) } } launch(coroutineContext) { repeat(10) { i -> delay(100) printer.send(i) } printer.close() } } Sequential!
  104. 104. Interacting with an actor fun main(args: Array<String>) = runBlocking<Unit> { val printer = actor<Int>(coroutineContext) { for (i in channel) { println(i) } } launch(coroutineContext) { repeat(10) { i -> delay(100) printer.send(i) } printer.close() } }
  105. 105. References
  106. 106. Guide to kotlinx.coroutines by example https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md • Basics • Cancellation and Timeouts • Composition • Coroutine contexts • Channels • Shared Mutable State and Concurrency • Select expressions
  107. 107. #kotlinconf17 relizarov elizarov at JetBrains Roman Elizarov Thank you Any questions?

×