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