$30 off During Our Annual Pro Sale. View Details »

Asynchronous Programming With Kotlin Coroutines

Asynchronous Programming With Kotlin Coroutines

Loveleen Kaur

May 08, 2023
Tweet

More Decks by Loveleen Kaur

Other Decks in Technology

Transcript

  1. Loveleen Kaur
    Android Educator
    Software Engineer, Astrotalk
    Asynchronous Programming
    With Kotlin Coroutines

    View Slide

  2. ● Loveleen Kaur
    ● Software Engineer - Android @ Astrotalk
    ● Core Team Member @ GDG Chandigarh
    ● Android Educator @ Android Educators Community India
    ● Phd Research Scholar
    ● Develop Mobile Applications
    ● Technical Speaker
    ● Happy Android Developer :)
    Who am I? 󰠁

    View Slide

  3. Asynchronous
    programming

    View Slide

  4. How do we write code that waits
    for something most of the time?

    View Slide

  5. A toy problem
    Kotlin fun requestToken(): Token {
    // makes request for a token & waits
    return token // returns result when received
    }
    1

    View Slide

  6. fun requestToken(): Token { … }
    fun createPost(token: Token, item: Item): Post {
    // sends item to the server & waits
    return post // returns resulting post
    }
    A toy problem
    2
    Kotlin

    View Slide

  7. A toy problem
    Kotlin fun requestToken(): Token { … }
    fun createPost(token: Token, item: Item): Post { … }
    3 fun processPost(post: Post) {
    // does some local processing of result
    }

    View Slide

  8. fun requestToken(): Token { … }
    fun createPost(token: Token, item: Item): Post { … }
    fun processPost(post: Post) { … }
    A toy problem
    fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }
    1
    2
    3
    Can be done with
    threads!
    Kotlin

    View Slide

  9. fun requestToken(): Token {
    // makes request for a token
    // blocks the thread waiting for result
    return token // returns result when received
    }
    fun createPost(token: Token, item: Item): Post { … }
    fun processPost(post: Post) { … }
    fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }
    Threads Is anything wrong with it?

    View Slide

  10. How many threads we can have?
    100

    View Slide

  11. How many threads we can have?
    1000

    View Slide

  12. How many threads we can have?
    10 000

    View Slide

  13. How many threads we can have?
    100 000

    View Slide

  14. Callbacks to the rescue
    Sort of

    View Slide

  15. Callbacks: before
    fun requestToken(): Token {
    // makes request for a token & waits
    return token // returns result when received
    }
    1
    Callbacks: after
    1
    callback
    fun requestTokenAsync(cb: (Token) -> Unit) {
    // makes request for a token, invokes callback when done
    // returns immediately
    }

    View Slide

  16. Callbacks: before
    fun requestTokenAsync(cb: (Token) -> Unit) { … }
    fun createPost(token: Token, item: Item): Post {
    // sends item to the server & waits
    return post // returns resulting post
    }
    2
    Callbacks: after
    fun requestTokenAsync(cb: (Token) -> Unit) { … }
    fun createPostAsync(token: Token, item: Item,
    cb: (Post) -> Unit) {
    // sends item to the server, invokes callback when done
    // returns immediately
    }
    2
    callback

    View Slide

  17. Callbacks: before
    fun
    fun
    requestTokenAsync(cb: (Token)
    createPostAsync(token: Token,
    -> Unit) { … }
    item: Item,
    cb: (Post) -> Unit) { … }
    fun processPost(post: Post) { … }
    fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }

    View Slide

  18. Callbacks: after
    fun
    fun
    requestTokenAsync(cb: (Token)
    createPostAsync(token: Token,
    -> Unit) { … }
    item: Item,
    cb: (Post) -> Unit) { … }
    fun processPost(post: Post) { … }
    fun postItem(item: Item) {
    requestTokenAsync { token ->
    createPostAsync(token, item) { post ->
    processPost(post)
    }
    }
    } aka “callback hell”
    This is simplified. Handling
    exceptions makes it a real mess

    View Slide

  19. Kotlin
    Coroutines

    View Slide

  20. ● The Kotlin team defines coroutines as “lightweight threads”.
    ● They are sort of tasks that the actual threads can execute.
    ● Coroutines were added to Kotlin in version 1.3 and are based on
    established concepts from other languages.
    ● Kotlin coroutines introduce a new style of concurrency that can be used on
    Android to simplify async code.
    Coroutines

    View Slide

  21. Kotlin coroutines to the rescue
    Let’s get real

    View Slide

  22. Coroutines: before
    fun requestTokenAsync(): Promise {
    // makes request for a token
    // returns promise for a future result immediately
    }
    1
    Coroutines: after
    suspend fun requestToken(): Token {
    // makes request for a token & suspends
    return token // returns result when received
    }
    1

    View Slide

  23. Coroutines: before
    suspend fun requestToken(): Token { … }
    fun createPostAsync(token: Token, item: Item): Promise {
    // sends item to the server
    // returns promise for a future result immediately
    }
    2
    Coroutines: after
    suspend fun requestToken(): Token { … }
    suspend fun createPost(token: Token, item: Item): Post {
    // sends item to the server & suspends
    return post // returns result when received
    }
    2

    View Slide

  24. Coroutines: before
    fun postItem(item: Item) {
    requestTokenAsync()
    .thenCompose { token -> createPostAsync(token, item) }
    .thenAccept { post -> processPost(post) }
    }
    Coroutines: after
    suspend fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }
    suspension
    points

    View Slide

  25. • Regular loops
    for ((token, item) in list) {
    createPost(token, item)
    }
    Bonus features

    View Slide

  26. • Regular exception handing
    try {
    createPost(token, item)
    } catch (e: BadTokenException) {

    }
    Bonus features

    View Slide

  27. Bonus features
    • Regular higher-order functions
    file.readLines().forEach { line ->
    createPost(token, line.toItem())
    }
    • forEach, let, apply, repeat, filter, map, use, etc
    Everything like in blocking code

    View Slide

  28. Suspending functions

    View Slide

  29. Retrofit async
    interface Service {
    fun createPost(token: Token, item: Item): Call
    }

    View Slide

  30. Retrofit async
    interface Service {
    fun createPost(token: Token, item: Item):
    }
    Call
    future

    View Slide

  31. Retrofit async
    interface Service {
    fun createPost(token: Token, item: Item): Call
    }
    suspend fun createPost(token: Token, item: Item): Post =
    serviceInstance.createPost(token, item).await()

    View Slide

  32. Retrofit async
    suspend fun createPost(token: Token, item: Item): Post =
    serviceInstance.createPost(token, item).await()
    interface Service {
    fun createPost(token: Token, item: Item): Call
    }
    natural signature

    View Slide

  33. Retrofit async
    interface Service {
    fun createPost(token: Token, item: Item): Call
    }
    suspend fun createPost(token: Token, item: Item): Post =
    serviceInstance.createPost(token, item).await()
    Suspending extension function
    from integration library

    View Slide

  34. Coroutine builders

    View Slide

  35. Coroutines revisited
    suspend fun requestToken(): Token { … }
    suspend fun createPost(token: Token, item: Item): Post { … }
    fun processPost(post: Post) { … }
    suspend fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }

    View Slide

  36. Coroutines revisited
    suspend fun requestToken(): Token { … }
    suspend fun createPost(token: Token, item: Item): Post { … }
    fun processPost(post: Post) { … }
    fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }

    View Slide

  37. Coroutines revisited
    suspend fun requestToken(): Token { … }
    suspend fun createPost(token: Token, item: Item): Post { … }
    fun processPost(post: Post) { … }
    fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }
    Error: Suspend function 'requestToken' should be called only from
    a coroutine or another suspend function

    View Slide

  38. Coroutines revisited
    suspend fun requestToken(): Token { … }
    suspend fun createPost(token: Token, item: Item): Post { … }
    fun processPost(post: Post) { … }
    fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }
    Can suspend execution

    View Slide

  39. Coroutines revisited
    suspend fun requestToken(): Token { … }
    suspend fun createPost(token: Token, item: Item): Post { … }
    fun processPost(post: Post) { … }
    fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }
    Can suspend execution
    A regular function cannot

    View Slide

  40. Coroutines revisited
    suspend fun requestToken(): Token { … }
    suspend fun createPost(token: Token, item: Item): Post { … }
    fun processPost(post: Post) { … }
    Can suspend execution
    A regular function cannot
    fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }
    One cannot simply invoke
    a suspending function

    View Slide

  41. }
    Launch
    coroutine builder
    fun postItem(item: Item) {
    launch {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }

    View Slide

  42. fun postItem(item: Item) {
    launch {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    } Fire and forget!
    Returns immediately, coroutine
    works in background thread pool
    }

    View Slide

  43. fun postItem(item: Item) {
    launch {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }
    }

    View Slide

  44. fun postItem(item: Item) {
    launch(UI) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }
    }
    UI Context
    Just specify the context

    View Slide

  45. fun postItem(item: Item) {
    launch(UI) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }
    }
    UI Context
    And it gets executed on UI thread

    View Slide

  46. Where’s the magic of launch?

    View Slide

  47. fun launch(
    context: CoroutineContext = DefaultDispatcher,
    block: suspend () -> Unit
    ): Job { … }
    A regular function

    View Slide

  48. fun launch(
    context: CoroutineContext = DefaultDispatcher,
    block: suspend () -> Unit
    ): Job { … } suspending lambda

    View Slide

  49. async / await

    View Slide

  50. Kotlin-way
    Kotlin suspend fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
    }
    suspend fun requestToken(): Token { … }
    suspend fun createPost(token: Token, item: Item): Post { … }
    fun processPost(post: Post) { … }

    View Slide

  51. Classic-way
    C# approach to the same problem
    (also Python, TS, Dart, coming to
    JS)
    C# async Task postItem(Item item) {
    var token = await requestToken();
    var post = await createPost(token, item);
    processPost(post);
    }
    async Task requestToken() { … }
    async Task createPost(Token token, Item item) { … }
    void processPost(Post post) { … }

    View Slide

  52. Why no await keyword in
    Kotlin?
    The problem with async
    requestToken()
    VALID –> produces Task
    await requestToken()
    VALID –> produces Token
    concurrent behavior
    sequential behavior
    C#
    C#
    default

    View Slide

  53. Kotlin suspending functions
    are designed to imitate
    sequential behavior
    by default
    Concurrency is hard
    Concurrency has to be explicit

    View Slide

  54. Kotlin approach to async
    Concurrency where you need it

    View Slide

  55. Kotlin async function
    fun loadImageAsync(name: String): Deferred =
    async { … }
    Kotlin

    View Slide

  56. Kotlin async function
    fun loadImageAsync(name: String): Deferred =
    async { … }
    Kotlin
    A regular function

    View Slide

  57. Kotlin async function
    fun loadImageAsync(name: String): Deferred =
    async { … }
    Kotlin’s future type
    Kotlin

    View Slide

  58. Kotlin async function
    fun loadImageAsync(name: String): Deferred =
    async { … }
    async coroutine builder
    Kotlin

    View Slide

  59. Kotlin async function
    fun loadImageAsync(name: String): Deferred =
    async { … }
    val deferred1 = loadImageAsync(name1)
    val deferred2 = loadImageAsync(name2)
    Start multiple operations
    concurrently
    Kotlin

    View Slide

  60. Kotlin async function
    fun loadImageAsync(name: String): Deferred =
    async { … }
    val deferred1 = loadImageAsync(name1)
    val deferred2 = loadImageAsync(name2)
    and then wait for them
    val image1 = deferred1.await()
    val image2 = deferred2.await()
    await function
    Suspends until deferred is complete
    Kotlin

    View Slide

  61. Kotlin async function
    fun loadImageAsync(name: String): Deferred =
    async { … }
    val deferred1 = loadImageAsync(name1)
    val deferred2 = loadImageAsync(name2)
    val image1 = deferred1.await()
    val image2 = deferred2.await()
    val result = combineImages(image1, image2)
    Kotlin

    View Slide

  62. Using async function when needed
    suspend fun loadImage(name: String): Image { … }
    Is defined as suspending function, not async

    View Slide

  63. Using async function when needed
    suspend fun loadImage(name: String): Image { … }
    suspend fun loadAndCombine(name1: String, name2: String): Image {
    val deferred1 = async { loadImage(name1) }
    val deferred2 = async { loadImage(name2) }
    return combineImages(deferred1.await(), deferred2.await())
    }

    View Slide

  64. Using async function when needed
    suspend fun loadImage(name: String): Image { … }
    suspend fun loadAndCombine(name1: String, name2: String): Image {
    async
    val deferred1 = { loadImage(name1) }
    val deferred2 = async { loadImage(name2) }
    return combineImages(deferred1.await(), deferred2.await())
    }

    View Slide

  65. Using async function when needed
    suspend fun loadImage(name: String): Image { … }
    suspend fun loadAndCombine(name1: String, name2: String): Image {
    val deferred1 = async { loadImage(name1) }
    async
    val deferred2 = { loadImage(name2) }
    return combineImages(deferred1.await(), deferred2.await())
    }

    View Slide

  66. Using async function when needed
    suspend fun loadImage(name: String): Image { … }
    suspend fun loadAndCombine(name1: String, name2: String): Image {
    val deferred1 = async { loadImage(name1) }
    val deferred2 = async { loadImage(name2) }
    return combineImages(deferred1.await(), deferred2.await())
    }

    View Slide

  67. Kotlin approach to async
    requestToken()
    VALID –> produces Token
    async { requestToken() }
    VALID –> produces Deferred
    sequential behavior
    concurrent behavior
    Kotlin
    Kotlin
    default

    View Slide

  68. Coroutines

    View Slide

  69. What are coroutines
    conceptually?

    View Slide

  70. What are coroutines
    conceptually?
    Coroutines are like very light-weight threads

    View Slide

  71. fun main(args: Array) = runBlocking {
    val jobs = List(100_000) {
    launch {
    delay(1000L)
    print(".")
    }
    }
    jobs.forEach { it.join() }
    }
    Example

    View Slide

  72. fun main(args: Array) = runBlocking {
    val jobs = List(100_000) {
    launch {
    delay(1000L)
    print(".")
    }
    }
    jobs.forEach { it.join() }
    }
    Example
    This coroutine builder runs coroutine
    in the context of invoker thread

    View Slide

  73. fun main(args: Array) = runBlocking {
    val jobs = List(100_000) {
    launch {
    delay(1000L)
    print(".")
    }
    }
    jobs.forEach { it.join() }
    }
    Example

    View Slide

  74. fun main(args: Array) = runBlocking {
    val jobs = List(100_000) {
    launch {
    delay(1000L)
    print(".")
    }
    }
    jobs.forEach { it.join() }
    }
    Example

    View Slide

  75. fun main(args: Array) = runBlocking {
    val jobs = List(100_000) {
    launch {
    delay(1000L)
    print(".")
    }
    }
    jobs.forEach { it.join() }
    }
    Example
    Suspends for 1 second

    View Slide

  76. fun main(args: Array) = runBlocking {
    val jobs = List(100_000) {
    launch {
    delay(1000L)
    print(".")
    }
    }
    jobs.forEach { it.join() }
    }
    Example
    We can join a job
    just like a thread

    View Slide

  77. Example
    Try that with 100k threads!
    fun main(args: Array) = runBlocking {
    val jobs = List(100_000) {
    launch {
    delay(1000L)
    print(".")
    }
    }
    jobs.forEach { it.join() }
    }
    Prints 100k dots after one second delay

    View Slide

  78. fun main(args: Array) =
    val jobs = List(100_000) {
    runBlocking {
    launch {
    delay(1000L)
    print(".")
    }
    }
    jobs.forEach { it.join() }
    }
    Example

    View Slide

  79. fun main(args: Array) {
    val jobs = List(100_000) {
    thread {
    Thread.sleep(1000L)
    print(".")
    }
    }
    jobs.forEach { it.join() }
    }
    Example

    View Slide

  80. Library vs Language

    View Slide

  81. Classic async
    async/await
    generate/yield
    Keywords

    View Slide

  82. Kotlin coroutines
    suspend Modifier

    View Slide

  83. Kotlin coroutines
    Standard
    library

    View Slide

  84. Kotlin coroutines
    Standard
    library
    kotlinx-coroutines
    launch, async,
    runBlocking, future, delay,
    Job, Deferred, etc
    http://github.com/kotlin/kotlinx.coroutines

    View Slide

  85. Thank you
    Loveleen Kaur
    https://www.linkedin.com/in/loveleen-kaur/
    https://twitter.com/loveleen_nancy

    View Slide