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

Kotlin Coroutines

Kotlin Coroutines

introduction to coroutines in Kotlin
presented at Droidcon Berlin
https://www.youtube.com/watch?v=nugOMl29K3k

Svetlana Isakova

September 05, 2017
Tweet

More Decks by Svetlana Isakova

Other Decks in Programming

Transcript

  1. Kotlin Coroutines • the key new feature in Kotlin 1.1

    • simplify asynchronous programming
  2. Coroutine a main routine and a subroutine vs coroutines, which

    call on each other • the term from 1960s • was used in “The Art of Computer Programming” by Donald Knuth
  3. C# way async Task ProcessImage(String url) { var image =

    await LoadImage(url); SetImage(image); } Kotlin way fun processImage() = async { val image = loadImageAsync().await() setImage(image) }
  4. Coroutine is similar to a thread Thread: • a sequence

    of instructions Multiple threads: • can be executed concurrently • share resources such as memory Coroutine: coroutines:
  5. Executor • fixed number of threads • adding tasks •

    but: difficult to manage dependencies
  6. 1 2 3 • computation that can be suspended •

    thread is not blocked! Coroutines
  7. 1 2 3 • computation that can be suspended •

    thread is not blocked! UI UI UI Coroutines
  8. fun loadImageAsync() = async { /* do the work */

    } fun processImage() = async { val image = loadImageAsync().await() setImage(image) } Back to image example
  9. suspending call fun processImage() = async { val image =

    loadImageAsync().await() setImage(image) } await suspends computation
  10. fun loadImageAsync(): Deferred<Image> = async { /* do the work

    */ } interface Deferred<out T> {
 suspend fun await(): T
 } await is a suspend function
  11. await suspends computation fun processImage() = async { val image

    = loadImageAsync().await() setImage(image) }
  12. fun processImage() = async { val deferred = loadImageAsync() val

    image = deferred.await() setImage(image) } await suspends computation
  13. await suspends computation processImage loadImageAsync 1 fun processImage() = async

    { val deferred = loadImageAsync() val image = deferred.await() setImage(image) }
  14. await suspends computation processImage loadImageAsync 1 loadImageAsync processImage 2 await

    fun processImage() = async { val deferred = loadImageAsync() val image = deferred.await() setImage(image) }
  15. await suspends computation loadImageAsync processImage 2 loadImageAsync processImage 3 …and

    continues it when result is ready fun processImage() = async { val deferred = loadImageAsync() val image = deferred.await() setImage(image) }
  16. fun processImage() = async(UI) {
 val deferred = loadImageAsync()
 //

    do other work
 val image = deferred.await() showImage(image)
 } Suspension might not happen if the result is already available processImage loadImageAsync await 1 2 processImage loadImageAsync
  17. 1 2 3 fun overlay(first: Image, second: Image): Image fun

    overlayAsync() = async(CommonPool) {
 val first = loadImageAsync("green")
 val second = loadImageAsync("red")
 overlay(first.await(), second.await())
 } Two asynchronous computations overlayAsync overlayAsync overlayAsync
  18. button.onClick {
 launch(UI) {
 val image = overlayAsync().await()
 showImage(image)
 }


    } Computation on UI thread 1 2 3 overlayAsync overlayAsync overlayAsync UI UI UI
  19. await rethrows exceptions val task = async(CommonPool) { ...
 throw

    MyException()
 }
 try {
 task.await()
 } catch (e: MyException) {
 ...
 } 1 3 throw e catch (e) 2
  20. val job = launch(CommonPool) {
 while (isActive) {
 ...
 }


    }
 job.cancel() Check cancellation explicitly in computation code
  21. Library suspend functions like await, delay check for cancellation val

    job = launch(CommonPool) {
 delay(1000)
 task.await()
 }
 job.cancel()
  22. You can run the code without cancellation val job =

    launch(CommonPool) { try { ... } finally { run(NonCancellable) { // this code won’t be cancelled } } } job.cancel()
  23. Example: simple consecutive logic fun login(credentials: Credentials): UserID fun loadUserData(userID:

    UserID): UserData fun showData(data: UserData) fun showUserInfo(cred: Credentials) { val userID = login(credentials) val userData = loadUserData(userID) showData(userData) }
  24. Rewrite with async/await fun login(credentials: Credentials): Deferred<UserID> fun loadUserData(userID: UserID):

    Deferred<UserData> fun showData(data: UserData) fun showUserInfo(credentials: Credentials) = async(CommonPool) { val userID = login(credentials).await() val userData = loadUserData(userID).await() showData(userData) }
  25. Rewrite with suspend functions suspend fun login(credentials: Credentials): UserID suspend

    fun loadUserData(userID: UserID): UserData fun showData(data: UserData) suspend fun showUserInfo(credentials: Credentials) { val userID = login(credentials) val userData = loadUserData(userID) showData(userData) }
  26. Q: Where can I call suspend functions? A: Inside other

    suspend functions and inside coroutine builders (in fact: inside suspend lambdas).
  27. val job = launch(CommonPool) { … } job.join() val deferred

    = async(CommonPool) { … } deferred.await() runBlocking { … } Coroutine Builders start a coroutine
  28. fun launch( context: CoroutineContext, block: suspend CoroutineScope.() -> Unit ):

    Job fun <T> async( context: CoroutineContext, block: suspend CoroutineScope.() -> T ) : Deferred<T> suspend lambdas
  29. fun showUserInfo(credentials: Credentials) = launch(UI) {
 val userID = login(credentials)


    val data = loadUserData(userID)
 val image = async(CommonPool) {
 loadImage(data.imageID)
 }
 showData(data)
 showImage(image.await())
 } Nested coroutines
  30. suspend fun foo(): Int suspend fun foo(continuation: Continuation<Int>): Int Hidden

    parameter: continuation Continuation is a generic callback interface: public interface Continuation<in T> { public val context: CoroutineContext public fun resume(value: T) public fun resumeWithException(exception: Throwable) }
  31. Q: Can I call a suspend function from Java? suspend

    fun foo(continuation: Continuation<Int>): Int A: You don’t want to call it directly (because of Continuation parameter).
  32. suspend fun foo(): Int fun fooAsync(): CompletableFuture<Int> = future {

    foo() } fun fooAsync(): Single<Int> = rxSingle(CommonPool) { foo() } To call suspend foo from Java wrap it into fooAsync if you have Java 8 if you have RxJava
  33. fun loadUserData(userId: UserID, callback: (UserData?, Throwable?) -> Unit) suspend fun

    loadUserData(userId: UserID): UserData { return suspendCoroutine { continuation -> loadUserData(userId) { data, exception -> if (data != null) { continuation.resume(data) } else { continuation.resumeWithException(exception!!) } } } } Wrapping third-party callback-based API
  34. fun loadUserDataRx(userId: UserID): Single<UserData> suspend fun loadUserData(userId: UserID): UserData {

    return suspendCoroutine { continuation -> loadUserDataRx(userId) .doOnSuccess { continuation.resume(it) } .doOnError { continuation.resumeWithException(it) } .subscribe() } } Wrapping a function returning rx.Single or just loadUserDataRx(userId).await()
  35. Example: simple consecutive logic fun login(credentials: Credentials): UserID fun loadUserData(userID:

    UserID): UserData fun showData(data: UserData) fun showUserInfo(credentials: Credentials) { val userID = login(credentials) val userData = loadUserData(userID) showData(userData) }
  36. Rewrite with suspend functions suspend fun login(credentials: Credentials): UserID suspend

    fun loadUserData(userID: UserID): UserData fun showData(data: UserData) suspend fun showUserInfo(credentials: Credentials) { val userID = login(credentials) val userData = loadUserData(userID) showData(userData) }
  37. Rewrite with CompletableFuture fun loginAsync(credentials: Credentials): CompletableFuture<UserID> fun loadUserDataAsync(userID: UserID):

    CompletableFuture<UserData> fun showData(data: UserData) fun showUserInfo(credentials: Credentials) { loginAsync(credentials) .thenCompose { loadUserDataAsync(it) } .thenAccept { showData(it) } }
  38. Rewrite with RxJava fun login(credentials: Credentials): Single<UserID> fun loadUserData(userID: UserID):

    Single<UserData> fun showData(data: UserData) fun showUserInfo(credentials: Credentials) { login(credentials) .flatMap { loadUserData(it) } .doOnSuccess { showData(it) } .subscribe() }
  39. “Observables are great, but in many cases they’re kind of

    overkill.” somewhere on the Internet
  40. Experimental status of Coroutines • We want the community to

    try it! • Migration aids will be provided • Old code will continue to work via the support library