Kotlin Coroutines
Asynchronous programming with ease
Slide 2
Slide 2 text
How do we write code that waits for
something most of the time?
Slide 3
Slide 3 text
Asynchronous Programming with
Callbacks
Slide 4
Slide 4 text
Callbacks: before
fun requestToken(): Token {
// makes request for a token & waits
return token // returns result when received
}
1
Slide 5
Slide 5 text
Callbacks: after
fun requestTokenAsync(cb: (Token) -> Unit) {
// makes request for a token, invokes callback when done
// returns immediately
}
1
callback
Slide 6
Slide 6 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
Slide 7
Slide 7 text
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 8
Slide 8 text
Callbacks: before
fun requestTokenAsync(cb: (Token) -> Unit) { … }
fun createPostAsync(token: Token, item: Item,
cb: (Post) -> Unit) { … }
fun processPost(post: Post) { … }
fun postItem(item: Item) {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
Slide 9
Slide 9 text
Callbacks: after
fun requestTokenAsync(cb: (Token) -> Unit) { … }
fun createPostAsync(token: Token, 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 10
Slide 10 text
Futures/Promises
make it nicer
Slide 11
Slide 11 text
Futures: before
fun requestTokenAsync(cb: (Token) -> Unit) {
// makes request for a token, invokes callback when done
// returns immediately
}
1
Slide 12
Slide 12 text
Futures: after
fun requestTokenAsync(): Promise {
// makes request for a token
// returns promise for a future result immediately
}
1
future
Slide 13
Slide 13 text
Futures: before
fun requestTokenAsync(): Promise { … }
fun createPostAsync(token: Token, item: Item,
cb: (Post) -> Unit) {
// sends item to the server, invokes callback when done
// returns immediately
}
2
Slide 14
Slide 14 text
Futures: after
fun requestTokenAsync(): Promise { … }
fun createPostAsync(token: Token, item: Item): Promise {
// sends item to the server
// returns promise for a future result immediately
}
future
2
Slide 15
Slide 15 text
Futures: before
fun requestTokenAsync(): Promise { … }
fun createPostAsync(token: Token, item: Item): Promise …
fun processPost(post: Post) { … }
fun postItem(item: Item) {
requestTokenAsync { token ->
createPostAsync(token, item) { post ->
processPost(post)
}
}
}
Slide 16
Slide 16 text
Futures: after
fun requestTokenAsync(): Promise { … }
fun createPostAsync(token: Token, item: Item): Promise …
fun processPost(post: Post) { … }
fun postItem(item: Item) {
requestTokenAsync()
.thenCompose { token -> createPostAsync(token, item) }
.thenAccept { post -> processPost(post) }
}
Slide 17
Slide 17 text
Futures: after
fun requestTokenAsync(): Promise { … }
fun createPostAsync(token: Token, item: Item): Promise …
fun processPost(post: Post) { … }
fun postItem(item: Item) {
requestTokenAsync()
.thenCompose { token -> createPostAsync(token, item) }
.thenAccept { post -> processPost(post) }
}
Callbacks are still here
Slide 18
Slide 18 text
Futures: after
fun requestTokenAsync(): Promise { … }
fun createPostAsync(token: Token, item: Item): Promise …
fun processPost(post: Post) { … }
fun postItem(item: Item) {
requestTokenAsync()
.thenCompose { token -> createPostAsync(token, item) }
.thenAccept { post -> processPost(post) }
}
Composable &
propagates exceptions
No nesting indentation Callbacks are still here
Slide 19
Slide 19 text
Futures: after
fun requestTokenAsync(): Promise { … }
fun createPostAsync(token: Token, item: Item): Promise …
fun processPost(post: Post) { … }
fun postItem(item: Item) {
requestTokenAsync()
.thenCompose { token -> createPostAsync(token, item) }
.thenAccept { post -> processPost(post) }
}
But all those operators…
Slide 20
Slide 20 text
Kotlin coroutines
to the rescue
Slide 21
Slide 21 text
Coroutines: before
fun requestTokenAsync(): Promise {
// makes request for a token
// returns promise for a future result immediately
}
1
Slide 22
Slide 22 text
Coroutines: after
suspend fun requestToken(): Token {
// makes request for a token & suspends
return token // returns result when received
}
1
natural signature
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
Slide 24
Slide 24 text
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
natural signature
Slide 25
Slide 25 text
Coroutines: before
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
fun processPost(post: Post) { … }
fun postItem(item: Item) {
requestTokenAsync()
.thenCompose { token -> createPostAsync(token, item) }
.thenAccept { post -> processPost(post) }
}
Slide 26
Slide 26 text
Coroutines: after
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 27
Slide 27 text
Coroutines: after
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)
}
Like regular code
Slide 28
Slide 28 text
Coroutines: after
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)
}
suspension
points
Slide 29
Slide 29 text
• Regular loops
Bonus features
for ((token, item) in list) {
createPost(token, item)
}
Kotlin suspending functions
callback
Kotlin
Java/JVM
Continuation is a generic callback interface
suspend fun createPost(token: Token, item: Item): Post { … }
Object createPost(Token token, Item item, Continuation cont) { … }
interface Continuation {
val context: CoroutineContext
fun resumeWith(result: Result)
}
Slide 36
Slide 36 text
Kotlin suspending functions
callback
Kotlin
Java/JVM
suspend fun createPost(token: Token, item: Item): Post { … }
Object createPost(Token token, Item item, Continuation cont) { … }
interface Continuation {
val context: CoroutineContext
fun resumeWith(result: Result)
}
Slide 37
Slide 37 text
Kotlin suspending functions
callback
Kotlin
Java/JVM
suspend fun createPost(token: Token, item: Item): Post { … }
Object createPost(Token token, Item item, Continuation cont) { … }
interface Continuation {
val context: CoroutineContext
fun resumeWith(result: Result)
}
Slide 38
Slide 38 text
Integration
Zoo of futures on JVM
Slide 39
Slide 39 text
interface Service {
fun createPost(token: Token, item: Item): Call
}
Retrofit async
Slide 40
Slide 40 text
interface Service {
fun createPost(token: Token, item: Item): Call
}
suspend fun createPost(token: Token, item: Item): Post =
serviceInstance.createPost(token, item).await()
natural signature
Slide 41
Slide 41 text
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 42
Slide 42 text
suspend fun Call.await(): T {
…
}
Slide 43
Slide 43 text
Callbacks everywhere
suspend fun Call.await(): T {
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
// todo
}
override fun onFailure(call: Call, t: Throwable) {
// todo
}
})
}
Slide 44
Slide 44 text
suspend fun Call.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call, t: Throwable) {
cont.resumeWithException(t)
}
})
}
Slide 45
Slide 45 text
suspend fun suspendCoroutine(block: (Continuation) -> Unit): T
Slide 46
Slide 46 text
suspend fun suspendCoroutine(block: (Continuation) -> Unit): T
Slide 47
Slide 47 text
suspend fun suspendCoroutine(block: (Continuation) -> Unit): T
Regular function
Inspired by call/cc from Scheme
Slide 48
Slide 48 text
suspend fun Call.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call, t: Throwable) {
cont.resumeWithException(t)
}
})
}
Slide 49
Slide 49 text
Install callback
suspend fun Call.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call, t: Throwable) {
cont.resumeWithException(t)
}
})
}
Slide 50
Slide 50 text
Install callback
suspend fun Call.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call, t: Throwable) {
cont.resumeWithException(t)
}
})
}
Slide 51
Slide 51 text
Analyze response
suspend fun Call.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call, t: Throwable) {
cont.resumeWithException(t)
}
})
}
Slide 52
Slide 52 text
Analyze response
suspend fun Call.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call, t: Throwable) {
cont.resumeWithException(t)
}
})
}
That’s all
Slide 53
Slide 53 text
Coroutine builders
Slide 54
Slide 54 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 55
Slide 55 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 56
Slide 56 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 57
Slide 57 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 58
Slide 58 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 59
Slide 59 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
One cannot simply invoke a
suspending function
Slide 60
Slide 60 text
Launch
fun postItem(item: Item) {
GlobalScope.launch {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
}
coroutine builder
Slide 61
Slide 61 text
fun postItem(item: Item) {
GlobalScope.launch {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
}
Fire and forget!
Slide 62
Slide 62 text
fun postItem(item: Item) {
GlobalScope.launch {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
}
Fire and forget!
We launch for its
side effects
Slide 63
Slide 63 text
fun postItem(item: Item) {
GlobalScope.launch {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
}
Returns immediately, coroutine works
in background thread pool
Slide 64
Slide 64 text
fun postItem(item: Item) {
GlobalScope.launch {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
}
Slide 65
Slide 65 text
fun postItem(item: Item) {
GlobalScope.launch(Dispatchers.Main) {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
}
Dispatcher
Just specify the dispatcher
Slide 66
Slide 66 text
fun postItem(item: Item) {
GlobalScope.launch(Dispatchers.Main) {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
}
Dispatcher
And it gets executed on the Main thread
Slide 67
Slide 67 text
Where’s the magic of launch?
Slide 68
Slide 68 text
fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend () -> Unit
): Job { … }
A regular function
Slide 69
Slide 69 text
fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend () -> Unit
): Job { … } suspending lambda
Slide 70
Slide 70 text
fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend () -> Unit
): Job { … }
Slide 71
Slide 71 text
async / await
The classic approach
Slide 72
Slide 72 text
Kotlin-way
suspend fun postItem(item: Item) {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
Kotlin
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
fun processPost(post: Post) { … }
Slide 73
Slide 73 text
async Task postItem(Item item) {
var token = await requestToken();
var post = await createPost(token, item);
processPost(post);
}
Classic-way
C# approach to the same problem (also
Python, TS, Dart, coming to JS)
C#
async Task requestToken() { … }
async Task createPost(Token token, Item item) { … }
void processPost(Post post) { … }
Slide 74
Slide 74 text
async Task postItem(Item item) {
var token = await requestToken();
var post = await createPost(token, item);
processPost(post);
}
Classic-way
mark with async
C#
async Task requestToken() { … }
async Task createPost(Token token, Item item) { … }
void processPost(Post post) { … }
Slide 75
Slide 75 text
async Task postItem(Item item) {
var token = await requestToken();
var post = await createPost(token, item);
processPost(post);
}
Classic-way
use await to suspend
C#
async Task requestToken() { … }
async Task createPost(Token token, Item item) { … }
void processPost(Post post) { … }
Use-case for async
var promise1 = loadImageAsync(name1);
var promise2 = loadImageAsync(name2);
async Task loadImageAsync(String name) { … }
Start multiple operations
concurrently
C#
Slide 82
Slide 82 text
Use-case for async
var promise1 = loadImageAsync(name1);
var promise2 = loadImageAsync(name2);
var image1 = await promise1;
var image2 = await promise2;
async Task loadImageAsync(String name) { … }
and then wait for them
C#
Slide 83
Slide 83 text
Use-case for async
var result = combineImages(image1, image2);
C#
var promise1 = loadImageAsync(name1);
var promise2 = loadImageAsync(name2);
var image1 = await promise1;
var image2 = await promise2;
async Task loadImageAsync(String name) { … }
Slide 84
Slide 84 text
Kotlin async function
fun loadImageAsync(name: String): Deferred =
GlobalScope.async { … }
Kotlin
Slide 85
Slide 85 text
Kotlin async function
fun loadImageAsync(name: String): Deferred =
GlobalScope.async { … }
Kotlin
A regular function
Slide 86
Slide 86 text
Kotlin async function
fun loadImageAsync(name: String): Deferred =
GlobalScope.async { … }
Kotlin’s future type
Kotlin
Slide 87
Slide 87 text
Kotlin async function
fun loadImageAsync(name: String): Deferred =
GlobalScope.async { … }
async coroutine builder
Kotlin
Slide 88
Slide 88 text
fun loadImageAsync(name: String): Deferred =
GlobalScope.async { … }
val deferred1 = loadImageAsync(name1)
val deferred2 = loadImageAsync(name2)
Start multiple operations
concurrently
Kotlin
Slide 89
Slide 89 text
fun loadImageAsync(name: String): Deferred =
GlobalScope.async { … }
val deferred1 = loadImageAsync(name1)
val deferred2 = loadImageAsync(name2)
val image1 = deferred1.await()
val image2 = deferred2.await()
and then wait for them
await function
Suspends until deferred is complete
Kotlin
Slide 90
Slide 90 text
fun loadImageAsync(name: String): Deferred =
GlobalScope.async { … }
val deferred1 = loadImageAsync(name1)
val deferred2 = loadImageAsync(name2)
val image1 = deferred1.await()
val image2 = deferred2.await()
val result = combineImages(image1, image2)
Kotlin
But what if this crashes?
Crash?
Slide 91
Slide 91 text
fun loadImageAsync(name: String): Deferred =
GlobalScope.async { … }
val deferred1 = loadImageAsync(name1)
val deferred2 = loadImageAsync(name2)
val image1 = deferred1.await()
val image2 = deferred2.await()
val result = combineImages(image1, image2)
Kotlin
Crash?
This one leaks
But what if this crashes?
Slide 92
Slide 92 text
Idiomatic: Using async function when needed
suspend fun loadImage(name: String): Image { … }
Is defined as suspending function, not async
Slide 93
Slide 93 text
Idiomatic: Using async function when needed
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String, name2: String): Image =
coroutineScope {
val deferred1 = async { loadImage(name1) }
val deferred2 = async { loadImage(name2) }
combineImages(deferred1.await(), deferred2.await())
}
Structured concurrency
Slide 94
Slide 94 text
Idiomatic: Using async function when needed
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String, name2: String): Image =
coroutineScope {
val deferred1 = async { loadImage(name1) }
val deferred2 = async { loadImage(name2) }
combineImages(deferred1.await(), deferred2.await())
}
Slide 95
Slide 95 text
Idiomatic: Using async function when needed
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String, name2: String): Image =
coroutineScope {
val deferred1 = async { loadImage(name1) }
val deferred2 = async { loadImage(name2) }
combineImages(deferred1.await(), deferred2.await())
}
Slide 96
Slide 96 text
Idiomatic: Using async function when needed
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String, name2: String): Image =
coroutineScope {
val deferred1 = async { loadImage(name1) }
val deferred2 = async { loadImage(name2) }
combineImages(deferred1.await(), deferred2.await())
}
Structured concurrency
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String, name2: String): Image =
coroutineScope {
val deferred1 = async { loadImage(name1) }
val deferred2 = async { loadImage(name2) }
combineImages(deferred1.await(), deferred2.await())
}
Slide 100
Slide 100 text
Crash
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String, name2: String): Image =
coroutineScope {
val deferred1 = async { loadImage(name1) }
val deferred2 = async { loadImage(name2) }
combineImages(deferred1.await(), deferred2.await())
}
Crashes?
Slide 101
Slide 101 text
Cancels scope
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String, name2: String): Image =
coroutineScope {
val deferred1 = async { loadImage(name1) }
val deferred2 = async { loadImage(name2) }
combineImages(deferred1.await(), deferred2.await())
}
Cancels
Slide 102
Slide 102 text
Cancels children
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String, name2: String): Image =
coroutineScope {
val deferred1 = async { loadImage(name1) }
val deferred2 = async { loadImage(name2) }
combineImages(deferred1.await(), deferred2.await())
}
Cancels
Slide 103
Slide 103 text
Kotlin suspending functions
may use concurrency internally,
but don’t leak it
Concurrency needs to be structured
Slide 104
Slide 104 text
Parallelism is optional
Slide 105
Slide 105 text
Beyond asynchronous code
Kotlin’s approach to generate/yield – synchronous coroutines
Slide 106
Slide 106 text
Fibonacci sequence
val fibonacci: Sequence = …
Slide 107
Slide 107 text
Fibonacci sequence
val fibonacci = sequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = tmp
}
}
Slide 108
Slide 108 text
Fibonacci sequence
val fibonacci = sequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = tmp
}
}
println(fibonacci.take(10).toList())
Slide 109
Slide 109 text
Fibonacci sequence
val fibonacci = sequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = tmp
}
}
println(fibonacci.take(10).toList())
>> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
Slide 110
Slide 110 text
Fibonacci sequence
val fibonacci = sequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = tmp
}
}
A coroutine builder with
restricted suspension
Slide 111
Slide 111 text
Fibonacci sequence
val fibonacci = sequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = tmp
}
}
A suspending function in
the scope of buildSequence
Slide 112
Slide 112 text
Fibonacci sequence
val fibonacci = sequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = tmp
}
}
println(fibonacci.take(10).toList())
Synchronous
Slide 113
Slide 113 text
Library vs Language
Keeping the core language small