Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

● 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? 󰠁

Slide 3

Slide 3 text

Asynchronous programming

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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 }

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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?

Slide 10

Slide 10 text

How many threads we can have? 100

Slide 11

Slide 11 text

How many threads we can have? 1000

Slide 12

Slide 12 text

How many threads we can have? 10 000

Slide 13

Slide 13 text

How many threads we can have? 100 000

Slide 14

Slide 14 text

Callbacks to the rescue Sort of …

Slide 15

Slide 15 text

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 }

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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) }

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Kotlin Coroutines

Slide 20

Slide 20 text

● 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

Slide 21

Slide 21 text

Kotlin coroutines to the rescue Let’s get real

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

• Regular exception handing try { createPost(token, item) } catch (e: BadTokenException) { … } Bonus features

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Suspending functions

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Coroutine builders

Slide 35

Slide 35 text

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) }

Slide 36

Slide 36 text

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) }

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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 }

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Where’s the magic of launch?

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

async / await

Slide 50

Slide 50 text

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) { … }

Slide 51

Slide 51 text

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) { … }

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Kotlin approach to async Concurrency where you need it

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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()) }

Slide 64

Slide 64 text

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()) }

Slide 65

Slide 65 text

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()) }

Slide 66

Slide 66 text

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()) }

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

Coroutines

Slide 69

Slide 69 text

What are coroutines conceptually?

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

Library vs Language

Slide 81

Slide 81 text

Classic async async/await generate/yield Keywords

Slide 82

Slide 82 text

Kotlin coroutines suspend Modifier

Slide 83

Slide 83 text

Kotlin coroutines Standard library

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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