Slide 1

Slide 1 text

Svetlana Isakova @sveta_isakova Kotlin Coroutines

Slide 2

Slide 2 text

Kotlin Coroutines • the key new feature in Kotlin 1.1 • simplify asynchronous programming

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Motivation: async/await

Slide 5

Slide 5 text

Motivation time consuming operation val image = loadImage(url) setImage(image)

Slide 6

Slide 6 text

Solution 1: callbacks loadImageAsync().whenComplete { image -> runOnUiThread { setImage(image) } }

Slide 7

Slide 7 text

Solution 2: async/await async(UI) { val image = loadImageAsync(url).await() setImage(image) }

Slide 8

Slide 8 text

val image = loadImageAsync(url).await() setImage(image) val image = loadImage(url) setImage(image) No callbacks!

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

coroutines async/await Language Library async/await - functions defined in the standard library

Slide 11

Slide 11 text

Threads & Coroutines

Slide 12

Slide 12 text

Coroutine is similar to a thread Thread: • a sequence of instructions Multiple threads: • can be executed concurrently • share resources such as memory Coroutine: coroutines:

Slide 13

Slide 13 text

Coroutine = “lightweight thread” Thread Coroutine

Slide 14

Slide 14 text

Coroutine computation that can be suspended computation thread

Slide 15

Slide 15 text

Coroutine computation that can be suspended

Slide 16

Slide 16 text

Why suspend?

Slide 17

Slide 17 text

Asynchronous computations: how?

Slide 18

Slide 18 text

New thread for every computation simple & straightforward, but too expensive

Slide 19

Slide 19 text

Executor • fixed number of threads • adding tasks • but: difficult to manage dependencies

Slide 20

Slide 20 text

Example: waiting for two asynchronous computations to complete

Slide 21

Slide 21 text

thenCombine / zip CompletableFutures / RxJava: managing dependencies the rest of computation in a callback

Slide 22

Slide 22 text

Stepping back: what’s wrong with blocking thread? idle too expensive

Slide 23

Slide 23 text

Stepping back: what’s wrong with blocking UI thread? blocked user is blocked UI

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

suspend function computation that can be suspended

Slide 27

Slide 27 text

fun loadImageAsync() = async { /* do the work */ } fun processImage() = async { val image = loadImageAsync().await() setImage(image) } Back to image example

Slide 28

Slide 28 text

async starts new computation fun loadImageAsync() = async { /* do the work */ } loadImageAsync

Slide 29

Slide 29 text

suspending call fun processImage() = async { val image = loadImageAsync().await() setImage(image) } await suspends computation

Slide 30

Slide 30 text

fun loadImageAsync(): Deferred = async { /* do the work */ } interface Deferred {
 suspend fun await(): T
 } await is a suspend function

Slide 31

Slide 31 text

await suspends computation fun processImage() = async { val image = loadImageAsync().await() setImage(image) }

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

await suspends computation loadImageAsync processImage 2 loadImageAsync processImage 3 …and continues it when result is ready On which thread?

Slide 37

Slide 37 text

Q: On which thread can the coroutine be continued? A: You specify that.

Slide 38

Slide 38 text

Continue in any thread from thread pool async(CommonPool) {
 ...
 } 1 2 3 3

Slide 39

Slide 39 text

Continue in the UI thread 1 2 3 async(UI) { ... } UI UI UI

Slide 40

Slide 40 text

Custom executor async(CustomContext) {
 ...
 } You can launch coroutine in the custom executor

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

button.onClick {
 launch(UI) {
 val image = overlayAsync().await()
 showImage(image)
 }
 } Computation on UI thread 1 2 3 overlayAsync overlayAsync overlayAsync UI UI UI

Slide 44

Slide 44 text

Q: What about handling errors (exceptions)? A: await rethrows the exception, so regular try/catch works.

Slide 45

Slide 45 text

await rethrows exceptions val task = async(CommonPool) { ...
 throw MyException()
 }
 try {
 task.await()
 } catch (e: MyException) {
 ...
 } 1 3 throw e catch (e) 2

Slide 46

Slide 46 text

Q: Can I cancel the coroutine? A: Yes (if coroutine is cancellable).

Slide 47

Slide 47 text

val job = launch(CommonPool) {
 while (isActive) {
 ...
 }
 }
 job.cancel() Check cancellation explicitly in computation code

Slide 48

Slide 48 text

Library suspend functions like await, delay check for cancellation val job = launch(CommonPool) {
 delay(1000)
 task.await()
 }
 job.cancel()

Slide 49

Slide 49 text

You can run the code without cancellation val job = launch(CommonPool) { try { ... } finally { run(NonCancellable) { // this code won’t be cancelled } } } job.cancel()

Slide 50

Slide 50 text

Programming with suspend functions

Slide 51

Slide 51 text

Q: Can I define my custom suspend functions? A: Yes.

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Q: Where can I call suspend functions? A: Inside other suspend functions and inside coroutine builders (in fact: inside suspend lambdas).

Slide 56

Slide 56 text

val job = launch(CommonPool) { … } job.join() val deferred = async(CommonPool) { … } deferred.await() runBlocking { … } Coroutine Builders start a coroutine

Slide 57

Slide 57 text

fun launch( context: CoroutineContext, block: suspend CoroutineScope.() -> Unit ): Job fun async( context: CoroutineContext, block: suspend CoroutineScope.() -> T ) : Deferred suspend lambdas

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Implementation details

Slide 60

Slide 60 text

suspend fun foo(): Int suspend fun foo(continuation: Continuation): Int Hidden parameter: continuation Continuation is a generic callback interface: public interface Continuation { public val context: CoroutineContext public fun resume(value: T) public fun resumeWithException(exception: Throwable) }

Slide 61

Slide 61 text

Coroutine body is compiled to a state machine 1 2 3 4 … state 1 state 2

Slide 62

Slide 62 text

Coroutines Regular world

Slide 63

Slide 63 text

Q: Can I call a suspend function from Java? suspend fun foo(continuation: Continuation): Int A: You don’t want to call it directly (because of Continuation parameter).

Slide 64

Slide 64 text

suspend fun foo(): Int fun fooAsync(): CompletableFuture = future { foo() } fun fooAsync(): Single = rxSingle(CommonPool) { foo() } To call suspend foo from Java wrap it into fooAsync if you have Java 8 if you have RxJava

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

fun loadUserDataRx(userId: UserID): Single 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()

Slide 67

Slide 67 text

RxJava / CompletableFuture vs Coroutines

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

Rewrite with CompletableFuture fun loginAsync(credentials: Credentials): CompletableFuture fun loadUserDataAsync(userID: UserID): CompletableFuture fun showData(data: UserData) fun showUserInfo(credentials: Credentials) { loginAsync(credentials) .thenCompose { loadUserDataAsync(it) } .thenAccept { showData(it) } }

Slide 71

Slide 71 text

Rewrite with RxJava fun login(credentials: Credentials): Single fun loadUserData(userID: UserID): Single fun showData(data: UserData) fun showUserInfo(credentials: Credentials) { login(credentials) .flatMap { loadUserData(it) } .doOnSuccess { showData(it) } .subscribe() }

Slide 72

Slide 72 text

“Observables are great, but in many cases they’re kind of overkill.” somewhere on the Internet

Slide 73

Slide 73 text

Kotlin coroutines RxJava RxJava & coroutines

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

coroutines async/await Language Library channels actors kotlinx.coroutines yield

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

kotlinx.coroutines • https://github.com/Kotlin/kotlinx.coroutines/ • Guide to kotlinx.coroutines by example • by Roman Elizarov (@relizarov)

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

• “Deep dive into coroutines on JVM” by Roman Elizarov

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

Have a nice Kotlin!