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

Asynchronous Programming With Kotlin Coroutines

Asynchronous Programming With Kotlin Coroutines

Avatar for Loveleen Kaur

Loveleen Kaur

May 08, 2023
Tweet

More Decks by Loveleen Kaur

Other Decks in Technology

Transcript

  1. • 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? 󰠁
  2. A toy problem Kotlin fun requestToken(): Token { // makes

    request for a token & waits return token // returns result when received } 1
  3. 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
  4. 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 }
  5. 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
  6. 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?
  7. 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 }
  8. 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
  9. 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) }
  10. 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
  11. • 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
  12. Coroutines: before fun requestTokenAsync(): Promise<Token> { // 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
  13. Coroutines: before suspend fun requestToken(): Token { … } fun

    createPostAsync(token: Token, item: Item): Promise<Post> { // 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
  14. 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
  15. • Regular exception handing try { createPost(token, item) } catch

    (e: BadTokenException) { … } Bonus features
  16. 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
  17. Retrofit async interface Service { fun createPost(token: Token, item: Item):

    Call<Post> } suspend fun createPost(token: Token, item: Item): Post = serviceInstance.createPost(token, item).await()
  18. Retrofit async suspend fun createPost(token: Token, item: Item): Post =

    serviceInstance.createPost(token, item).await() interface Service { fun createPost(token: Token, item: Item): Call<Post> } natural signature
  19. Retrofit async interface Service { fun createPost(token: Token, item: Item):

    Call<Post> } suspend fun createPost(token: Token, item: Item): Post = serviceInstance.createPost(token, item).await() Suspending extension function from integration library
  20. 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) }
  21. 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) }
  22. 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
  23. 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
  24. 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
  25. 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
  26. } Launch coroutine builder fun postItem(item: Item) { launch {

    val token = requestToken() val post = createPost(token, item) processPost(post) }
  27. 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 }
  28. fun postItem(item: Item) { launch { val token = requestToken()

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

    val post = createPost(token, item) processPost(post) } } UI Context Just specify the context
  30. 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
  31. 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) { … }
  32. 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<Token> requestToken() { … } async Task<Post> createPost(Token token, Item item) { … } void processPost(Post post) { … }
  33. Why no await keyword in Kotlin? The problem with async

    requestToken() VALID –> produces Task<Token> await requestToken() VALID –> produces Token concurrent behavior sequential behavior C# C# default
  34. Kotlin suspending functions are designed to imitate sequential behavior by

    default Concurrency is hard Concurrency has to be explicit
  35. Kotlin async function fun loadImageAsync(name: String): Deferred<Image> = async {

    … } val deferred1 = loadImageAsync(name1) val deferred2 = loadImageAsync(name2) Start multiple operations concurrently Kotlin
  36. Kotlin async function fun loadImageAsync(name: String): Deferred<Image> = 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
  37. Kotlin async function fun loadImageAsync(name: String): Deferred<Image> = async {

    … } val deferred1 = loadImageAsync(name1) val deferred2 = loadImageAsync(name2) val image1 = deferred1.await() val image2 = deferred2.await() val result = combineImages(image1, image2) Kotlin
  38. Using async function when needed suspend fun loadImage(name: String): Image

    { … } Is defined as suspending function, not async
  39. 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()) }
  40. 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()) }
  41. 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()) }
  42. 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()) }
  43. Kotlin approach to async requestToken() VALID –> produces Token async

    { requestToken() } VALID –> produces Deferred<Token> sequential behavior concurrent behavior Kotlin Kotlin default
  44. fun main(args: Array<String>) = runBlocking<Unit> { val jobs = List(100_000)

    { launch { delay(1000L) print(".") } } jobs.forEach { it.join() } } Example
  45. fun main(args: Array<String>) = runBlocking<Unit> { 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
  46. fun main(args: Array<String>) = runBlocking<Unit> { val jobs = List(100_000)

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

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

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

    { launch { delay(1000L) print(".") } } jobs.forEach { it.join() } } Example We can join a job just like a thread
  50. Example Try that with 100k threads! fun main(args: Array<String>) =

    runBlocking<Unit> { val jobs = List(100_000) { launch { delay(1000L) print(".") } } jobs.forEach { it.join() } } Prints 100k dots after one second delay
  51. fun main(args: Array<String>) = val jobs = List(100_000) { runBlocking<Unit>

    { launch { delay(1000L) print(".") } } jobs.forEach { it.join() } } Example
  52. fun main(args: Array<String>) { val jobs = List(100_000) { thread

    { Thread.sleep(1000L) print(".") } } jobs.forEach { it.join() } } Example
  53. Kotlin coroutines Standard library kotlinx-coroutines launch, async, runBlocking, future, delay,

    Job, Deferred, etc http://github.com/kotlin/kotlinx.coroutines