Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
elizarov at JetBrains
Roman Elizarov
Introduction to Coroutines
Asynchronous
programming
How do we write code that waits
for something most of the time?
A toy problem
Kotlin fun requestToken(): Token {
// makes request for a token & waits
return token // returns result when ...
fun requestToken(): Token { … }
fun createPost(token: Token, item: Item): Post {
// sends item to the server & waits
retur...
fun requestToken(): Token { … }
fun createPost(token: Token, item: Item): Post { … }
fun processPost(post: Post) {
// does...
fun requestToken(): Token { … }
fun createPost(token: Token, item: Item): Post { … }
fun processPost(post: Post) { … }
A t...
fun requestToken(): Token {
// makes request for a token
// blocks the thread waiting for result
return token // returns r...
How many threads we can have?
100 🙂
How many threads we can have?
1000 😅
How many threads we can have?
10 000 😩
How many threads we can have?
100 000 😵
Callbacks to the rescue
Sort of …
Callbacks: before
fun requestToken(): Token {
// makes request for a token & waits
return token // returns result when rec...
Callbacks: after
fun requestTokenAsync(cb: (Token) -> Unit) {
// makes request for a token, invokes callback when done
// ...
Callbacks: before
fun requestTokenAsync(cb: (Token) -> Unit) { … }
fun createPost(token: Token, item: Item): Post {
// sen...
Callbacks: after
fun requestTokenAsync(cb: (Token) -> Unit) { … }
fun createPostAsync(token: Token, item: Item,
cb: (Post)...
Callbacks: before
fun requestTokenAsync(cb: (Token) -> Unit) { … }
fun createPostAsync(token: Token, item: Item,
cb: (Post...
Callbacks: after
fun requestTokenAsync(cb: (Token) -> Unit) { … }
fun createPostAsync(token: Token, item: Item,
cb: (Post)...
Futures/Promises/Rx
to the rescue
Sort of …
Futures: before
fun requestTokenAsync(cb: (Token) -> Unit) {
// makes request for a token, invokes callback when done
// r...
Futures: after
fun requestTokenAsync(): Promise<Token> {
// makes request for a token
// returns promise for a future resu...
Futures: before
fun requestTokenAsync(): Promise<Token> { … }
fun createPostAsync(token: Token, item: Item,
cb: (Post) -> ...
Futures: after
fun requestTokenAsync(): Promise<Token> { … }
fun createPostAsync(token: Token, item: Item): Promise<Post> ...
Futures: before
fun requestTokenAsync(): Promise<Token> { … }
fun createPostAsync(token: Token, item: Item): Promise<Post>...
Futures: after
fun requestTokenAsync(): Promise<Token> { … }
fun createPostAsync(token: Token, item: Item): Promise<Post> ...
Futures: after
fun requestTokenAsync(): Promise<Token> { … }
fun createPostAsync(token: Token, item: Item): Promise<Post> ...
Kotlin coroutines to the rescue
Let’s get real
Coroutines: before
fun requestTokenAsync(): Promise<Token> {
// makes request for a token
// returns promise for a future ...
Coroutines: after
suspend fun requestToken(): Token {
// makes request for a token & suspends
return token // returns resu...
Coroutines: after
suspend fun requestToken(): Token {
// makes request for a token & suspends
return token // returns resu...
Coroutines: before
suspend fun requestToken(): Token { … }
fun createPostAsync(token: Token, item: Item): Promise<Post> {
...
Coroutines: after
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post {
// send...
Coroutines: before
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
fu...
Coroutines: after
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
fun...
Coroutines: after
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
fun...
Coroutines: after
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
fun...
• Regular loops
Bonus features
for ((token, item) in list) {
createPost(token, item)
}
• Regular exception handing
Bonus features
try {
createPost(token, item)
} catch (e: BadTokenException) {
…
}
• Regular higher-order functions
• forEach, let, apply, repeat, filter, map, use, etc
Bonus features
file.readLines().forE...
Suspending functions
Retrofit async
interface Service {
fun createPost(token: Token, item: Item): Call<Post>
}
Retrofit async
interface Service {
fun createPost(token: Token, item: Item): Call<Post>
}
future
Retrofit async
interface Service {
fun createPost(token: Token, item: Item): Call<Post>
}
suspend fun createPost(token: To...
Retrofit async
interface Service {
fun createPost(token: Token, item: Item): Call<Post>
}
suspend fun createPost(token: To...
Retrofit async
interface Service {
fun createPost(token: Token, item: Item): Call<Post>
}
suspend fun createPost(token: To...
Composition
Beyond sequential
val post = createPost(token, item)
Higher-order functions
val post = retryIO {
createPost(token, item)
}
Higher-order functions
val post = retryIO { createPost(token, item) }
suspend fun <T> retryIO(block: suspend () -> T): T {...
Higher-order functions
val post = retryIO { createPost(token, item) }
suspend fun <T> retryIO(block: suspend () -> T): T {...
Higher-order functions
val post = retryIO { createPost(token, item) }
suspend fun <T> retryIO(block: suspend () -> T): T {...
Higher-order functions
val post = retryIO { createPost(token, item) }
suspend fun <T> retryIO(block: suspend () -> T): T {...
Higher-order functions
val post = retryIO { createPost(token, item) }
suspend fun <T> retryIO(block: suspend () -> T): T {...
Coroutine builders
Coroutines revisited
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
...
Coroutines revisited
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
...
Coroutines revisited
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
...
Coroutines revisited
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
...
Coroutines revisited
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
...
Coroutines revisited
suspend fun requestToken(): Token { … }
suspend fun createPost(token: Token, item: Item): Post { … }
...
Launch
fun postItem(item: Item) {
launch {
val token = requestToken()
val post = createPost(token, item)
processPost(post)...
fun postItem(item: Item) {
launch {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
}
Fi...
fun postItem(item: Item) {
launch {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
}
fun postItem(item: Item) {
launch(UI) {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
...
fun postItem(item: Item) {
launch(UI) {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
...
Where’s the magic of launch?
fun launch(
context: CoroutineContext = DefaultDispatcher,
block: suspend () -> Unit
): Job { … }
A regular function
fun launch(
context: CoroutineContext = DefaultDispatcher,
block: suspend () -> Unit
): Job { … } suspending lambda
fun launch(
context: CoroutineContext = DefaultDispatcher,
block: suspend () -> Unit
): Job { … }
async / await
Kotlin-way
suspend fun postItem(item: Item) {
val token = requestToken()
val post = createPost(token, item)
processPost(po...
async Task postItem(Item item) {
var token = await requestToken();
var post = await createPost(token, item);
processPost(p...
async Task postItem(Item item) {
var token = await requestToken();
var post = await createPost(token, item);
processPost(p...
async Task postItem(Item item) {
var token = await requestToken();
var post = await createPost(token, item);
processPost(p...
async Task postItem(Item item) {
var token = await requestToken();
var post = await createPost(token, item);
processPost(p...
Why no await keyword in Kotlin?
The problem with async
requestToken() VALID –> produces Task<Token>
await requestToken() V...
Kotlin suspending functions
are designed to imitate
sequential behavior
by default
Concurrency is hard
Concurrency has to ...
Kotlin approach to async
Concurrency where you need it
Use-case for async
async Task<Image> loadImageAsync(String name) { … }C#
Use-case for async
var promise1 = loadImageAsync(name1);
var promise2 = loadImageAsync(name2);
async Task<Image> loadImage...
Use-case for async
var promise1 = loadImageAsync(name1);
var promise2 = loadImageAsync(name2);
var image1 = await promise1...
Use-case for async
var result = combineImages(image1, image2);
C#
var promise1 = loadImageAsync(name1);
var promise2 = loa...
Kotlin async function
fun loadImageAsync(name: String): Deferred<Image> =
async { … }
Kotlin
Kotlin async function
fun loadImageAsync(name: String): Deferred<Image> =
async { … }
Kotlin
A regular function
Kotlin async function
fun loadImageAsync(name: String): Deferred<Image> =
async { … }
Kotlin’s future type
Kotlin
Kotlin async function
fun loadImageAsync(name: String): Deferred<Image> =
async { … }
async coroutine builder
Kotlin
Kotlin async function
fun loadImageAsync(name: String): Deferred<Image> =
async { … }
val deferred1 = loadImageAsync(name1...
Kotlin async function
fun loadImageAsync(name: String): Deferred<Image> =
async { … }
val deferred1 = loadImageAsync(name1...
Kotlin async function
fun loadImageAsync(name: String): Deferred<Image> =
async { … }
val deferred1 = loadImageAsync(name1...
Using async function when needed
suspend fun loadImage(name: String): Image { … }
Is defined as suspending function, not a...
Using async function when needed
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String...
Using async function when needed
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String...
Using async function when needed
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String...
Using async function when needed
suspend fun loadImage(name: String): Image { … }
suspend fun loadAndCombine(name1: String...
Kotlin approach to async
requestToken() VALID –> produces Token
async { requestToken() } VALID –> produces Deferred<Token>...
Coroutines
What are coroutines
conceptually?
What are coroutines
conceptually?
Coroutines are like very light-weight threads
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.f...
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.f...
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.f...
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.f...
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.f...
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.f...
Demo
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.f...
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.f...
fun main(args: Array<String>) {
val jobs = List(100_000) {
thread {
Thread.sleep(1000L)
print(".")
}
}
jobs.forEach { it.j...
Demo
fun main(args: Array<String>) {
val jobs = List(100_000) {
thread {
Thread.sleep(1000L)
print(".")
}
}
jobs.forEach { it.j...
Java interop
CompletableFuture<Image> loadImageAsync(String name) { … }Java
CompletableFuture<Image> loadImageAsync(String name) { … }
CompletableFuture<Image> loadAndCombineAsync(String name1,
Stri...
CompletableFuture<Image> loadImageAsync(String name) { … }
CompletableFuture<Image> loadAndCombineAsync(String name1,
Stri...
CompletableFuture<Image> loadImageAsync(String name) { … }
CompletableFuture<Image> loadAndCombineAsync(String name1,
Stri...
CompletableFuture<Image> loadImageAsync(String name) { … }
fun loadAndCombineAsync(
name1: String,
name2: String
): Comple...
CompletableFuture<Image> loadImageAsync(String name) { … }
fun loadAndCombineAsync(
name1: String,
name2: String
): Comple...
CompletableFuture<Image> loadImageAsync(String name) { … }
fun loadAndCombineAsync(
name1: String,
name2: String
): Comple...
CompletableFuture<Image> loadImageAsync(String name) { … }
fun loadAndCombineAsync(
name1: String,
name2: String
): Comple...
CompletableFuture<Image> loadImageAsync(String name) { … }
fun loadAndCombineAsync(
name1: String,
name2: String
): Comple...
Beyond
asynchronous code
Fibonacci sequence
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next...
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = ...
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = ...
The same building blocks
fun <T> buildSequence(
builderAction: suspend SequenceBuilder<T>.() -> Unit
): Sequence<T> { … }
fun <T> buildSequence(
builderAction: suspend SequenceBuilder<T>.() -> Unit
): Sequence<T> { … }
Result is a synchronous s...
fun <T> buildSequence(
builderAction: suspend SequenceBuilder<T>.() -> Unit
): Sequence<T> { … }
Suspending lambda with re...
fun <T> buildSequence(
builderAction: suspend SequenceBuilder<T>.() -> Unit
): Sequence<T> { … }
@RestrictsSuspension
abst...
Synchronous
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = ...
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = ...
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = ...
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = ...
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = ...
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = ...
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = ...
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = ...
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = ...
Library vs Language
Classic async
async/await
generate/yield
Keywords
Kotlin coroutines
suspend Modifier
Kotlin coroutines
Standard
library
Kotlin coroutines
Standard
library
kotlinx-coroutines
launch, async,
runBlocking, future, delay,
Job, Deferred, etc
http:/...
Experimental status
Coroutines are here to stay
Backwards compatible inside 1.1 & 1.2
To be finalized in the future
#kotlinconf17
relizarov
elizarov at JetBrains
Roman Elizarov
Thank you
Any questions?
Upcoming SlideShare
Loading in …5
×

Introduction to Coroutines @ KotlinConf 2017

3,232 views

Published on

Introduction to Coroutines, presented at KotlinConf 2017; video is available at https://www.youtube.com/watch?v=_hfBv0a09Jc

Published in: Technology

Introduction to Coroutines @ KotlinConf 2017

  1. 1. elizarov at JetBrains Roman Elizarov Introduction to Coroutines
  2. 2. Asynchronous programming
  3. 3. How do we write code that waits for something most of the time?
  4. 4. A toy problem Kotlin fun requestToken(): Token { // makes request for a token & waits return token // returns result when received } 1
  5. 5. 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
  6. 6. fun requestToken(): Token { … } fun createPost(token: Token, item: Item): Post { … } fun processPost(post: Post) { // does some local processing of result } A toy problem 3 Kotlin
  7. 7. 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
  8. 8. 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) { … } Threads fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) } Is anything wrong with it?
  9. 9. How many threads we can have? 100 🙂
  10. 10. How many threads we can have? 1000 😅
  11. 11. How many threads we can have? 10 000 😩
  12. 12. How many threads we can have? 100 000 😵
  13. 13. Callbacks to the rescue Sort of …
  14. 14. Callbacks: before fun requestToken(): Token { // makes request for a token & waits return token // returns result when received } 1
  15. 15. Callbacks: after fun requestTokenAsync(cb: (Token) -> Unit) { // makes request for a token, invokes callback when done // returns immediately } 1 callback
  16. 16. 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
  17. 17. 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
  18. 18. 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) }
  19. 19. 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
  20. 20. Futures/Promises/Rx to the rescue Sort of …
  21. 21. Futures: before fun requestTokenAsync(cb: (Token) -> Unit) { // makes request for a token, invokes callback when done // returns immediately } 1
  22. 22. Futures: after fun requestTokenAsync(): Promise<Token> { // makes request for a token // returns promise for a future result immediately } 1 future
  23. 23. Futures: before fun requestTokenAsync(): Promise<Token> { … } fun createPostAsync(token: Token, item: Item, cb: (Post) -> Unit) { // sends item to the server, invokes callback when done // returns immediately } 2
  24. 24. Futures: after fun requestTokenAsync(): Promise<Token> { … } fun createPostAsync(token: Token, item: Item): Promise<Post> { // sends item to the server // returns promise for a future result immediately } future 2
  25. 25. Futures: before fun requestTokenAsync(): Promise<Token> { … } fun createPostAsync(token: Token, item: Item): Promise<Post> … fun processPost(post: Post) { … } fun postItem(item: Item) { requestTokenAsync { token -> createPostAsync(token, item) { post -> processPost(post) } } }
  26. 26. Futures: after fun requestTokenAsync(): Promise<Token> { … } fun createPostAsync(token: Token, item: Item): Promise<Post> … fun processPost(post: Post) { … } fun postItem(item: Item) { requestTokenAsync() .thenCompose { token -> createPostAsync(token, item) } .thenAccept { post -> processPost(post) } } Composable & propagates exceptions No nesting indentation
  27. 27. Futures: after fun requestTokenAsync(): Promise<Token> { … } fun createPostAsync(token: Token, item: Item): Promise<Post> … fun processPost(post: Post) { … } fun postItem(item: Item) { requestTokenAsync() .thenCompose { token -> createPostAsync(token, item) } .thenAccept { post -> processPost(post) } } But all those combinators…
  28. 28. Kotlin coroutines to the rescue Let’s get real
  29. 29. Coroutines: before fun requestTokenAsync(): Promise<Token> { // makes request for a token // returns promise for a future result immediately } 1
  30. 30. Coroutines: after suspend fun requestToken(): Token { // makes request for a token & suspends return token // returns result when received } 1
  31. 31. Coroutines: after suspend fun requestToken(): Token { // makes request for a token & suspends return token // returns result when received } 1 natural signature
  32. 32. 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
  33. 33. 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
  34. 34. 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) } }
  35. 35. 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) }
  36. 36. 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
  37. 37. 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
  38. 38. • Regular loops Bonus features for ((token, item) in list) { createPost(token, item) }
  39. 39. • Regular exception handing Bonus features try { createPost(token, item) } catch (e: BadTokenException) { … }
  40. 40. • Regular higher-order functions • forEach, let, apply, repeat, filter, map, use, etc Bonus features file.readLines().forEach { line -> createPost(token, line.toItem()) } Everything like in blocking code
  41. 41. Suspending functions
  42. 42. Retrofit async interface Service { fun createPost(token: Token, item: Item): Call<Post> }
  43. 43. Retrofit async interface Service { fun createPost(token: Token, item: Item): Call<Post> } future
  44. 44. 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()
  45. 45. 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() natural signature
  46. 46. 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
  47. 47. Composition Beyond sequential
  48. 48. val post = createPost(token, item)
  49. 49. Higher-order functions val post = retryIO { createPost(token, item) }
  50. 50. Higher-order functions val post = retryIO { createPost(token, item) } suspend fun <T> retryIO(block: suspend () -> T): T { var curDelay = 1000L // start with 1 sec while (true) { try { return block() } catch (e: IOException) { e.printStackTrace() // log the error } delay(curDelay) curDelay = (curDelay * 2).coerceAtMost(60000L) } }
  51. 51. Higher-order functions val post = retryIO { createPost(token, item) } suspend fun <T> retryIO(block: suspend () -> T): T { var curDelay = 1000L // start with 1 sec while (true) { try { return block() } catch (e: IOException) { e.printStackTrace() // log the error } delay(curDelay) curDelay = (curDelay * 2).coerceAtMost(60000L) } }
  52. 52. Higher-order functions val post = retryIO { createPost(token, item) } suspend fun <T> retryIO(block: suspend () -> T): T { var curDelay = 1000L // start with 1 sec while (true) { try { return block() } catch (e: IOException) { e.printStackTrace() // log the error } delay(curDelay) curDelay = (curDelay * 2).coerceAtMost(60000L) } } suspending lambda
  53. 53. Higher-order functions val post = retryIO { createPost(token, item) } suspend fun <T> retryIO(block: suspend () -> T): T { var curDelay = 1000L // start with 1 sec while (true) { try { return block() } catch (e: IOException) { e.printStackTrace() // log the error } delay(curDelay) curDelay = (curDelay * 2).coerceAtMost(60000L) } } Everything like in blocking code
  54. 54. Higher-order functions val post = retryIO { createPost(token, item) } suspend fun <T> retryIO(block: suspend () -> T): T { var curDelay = 1000L // start with 1 sec while (true) { try { return block() } catch (e: IOException) { e.printStackTrace() // log the error } delay(curDelay) curDelay = (curDelay * 2).coerceAtMost(60000L) } }
  55. 55. Coroutine builders
  56. 56. 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) }
  57. 57. 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) }
  58. 58. 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
  59. 59. 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
  60. 60. 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 executionA regular function cannot
  61. 61. 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 executionA regular function cannot One cannot simply invoke a suspending function
  62. 62. Launch fun postItem(item: Item) { launch { val token = requestToken() val post = createPost(token, item) processPost(post) } } coroutine builder
  63. 63. 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
  64. 64. fun postItem(item: Item) { launch { val token = requestToken() val post = createPost(token, item) processPost(post) } }
  65. 65. fun postItem(item: Item) { launch(UI) { val token = requestToken() val post = createPost(token, item) processPost(post) } } UI Context Just specify the context
  66. 66. 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
  67. 67. Where’s the magic of launch?
  68. 68. fun launch( context: CoroutineContext = DefaultDispatcher, block: suspend () -> Unit ): Job { … } A regular function
  69. 69. fun launch( context: CoroutineContext = DefaultDispatcher, block: suspend () -> Unit ): Job { … } suspending lambda
  70. 70. fun launch( context: CoroutineContext = DefaultDispatcher, block: suspend () -> Unit ): Job { … }
  71. 71. async / await
  72. 72. 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) { … }
  73. 73. 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<Token> requestToken() { … } async Task<Post> createPost(Token token, Item item) { … } void processPost(Post post) { … }
  74. 74. 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<Token> requestToken() { … } async Task<Post> createPost(Token token, Item item) { … } void processPost(Post post) { … }
  75. 75. 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<Token> requestToken() { … } async Task<Post> createPost(Token token, Item item) { … } void processPost(Post post) { … }
  76. 76. async Task postItem(Item item) { var token = await requestToken(); var post = await createPost(token, item); processPost(post); } Classic-way C# returns a future async Task<Token> requestToken() { … } async Task<Post> createPost(Token token, Item item) { … } void processPost(Post post) { … }
  77. 77. 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
  78. 78. Kotlin suspending functions are designed to imitate sequential behavior by default Concurrency is hard Concurrency has to be explicit
  79. 79. Kotlin approach to async Concurrency where you need it
  80. 80. Use-case for async async Task<Image> loadImageAsync(String name) { … }C#
  81. 81. Use-case for async var promise1 = loadImageAsync(name1); var promise2 = loadImageAsync(name2); async Task<Image> loadImageAsync(String name) { … } Start multiple operations concurrently C#
  82. 82. Use-case for async var promise1 = loadImageAsync(name1); var promise2 = loadImageAsync(name2); var image1 = await promise1; var image2 = await promise2; async Task<Image> loadImageAsync(String name) { … } and then wait for them C#
  83. 83. 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<Image> loadImageAsync(String name) { … }
  84. 84. Kotlin async function fun loadImageAsync(name: String): Deferred<Image> = async { … } Kotlin
  85. 85. Kotlin async function fun loadImageAsync(name: String): Deferred<Image> = async { … } Kotlin A regular function
  86. 86. Kotlin async function fun loadImageAsync(name: String): Deferred<Image> = async { … } Kotlin’s future type Kotlin
  87. 87. Kotlin async function fun loadImageAsync(name: String): Deferred<Image> = async { … } async coroutine builder Kotlin
  88. 88. Kotlin async function fun loadImageAsync(name: String): Deferred<Image> = async { … } val deferred1 = loadImageAsync(name1) val deferred2 = loadImageAsync(name2) Start multiple operations concurrently Kotlin
  89. 89. 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() and then wait for them await function Suspends until deferred is complete Kotlin
  90. 90. 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
  91. 91. Using async function when needed suspend fun loadImage(name: String): Image { … } Is defined as suspending function, not async
  92. 92. 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()) }
  93. 93. 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()) }
  94. 94. 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()) }
  95. 95. 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()) }
  96. 96. Kotlin approach to async requestToken() VALID –> produces Token async { requestToken() } VALID –> produces Deferred<Token> sequential behavior concurrent behavior Kotlin Kotlin default
  97. 97. Coroutines
  98. 98. What are coroutines conceptually?
  99. 99. What are coroutines conceptually? Coroutines are like very light-weight threads
  100. 100. fun main(args: Array<String>) = runBlocking<Unit> { val jobs = List(100_000) { launch { delay(1000L) print(".") } } jobs.forEach { it.join() } } Example
  101. 101. 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
  102. 102. fun main(args: Array<String>) = runBlocking<Unit> { val jobs = List(100_000) { launch { delay(1000L) print(".") } } jobs.forEach { it.join() } } Example
  103. 103. fun main(args: Array<String>) = runBlocking<Unit> { val jobs = List(100_000) { launch { delay(1000L) print(".") } } jobs.forEach { it.join() } } Example
  104. 104. 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
  105. 105. 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
  106. 106. Demo
  107. 107. fun main(args: Array<String>) = runBlocking<Unit> { val jobs = List(100_000) { launch { delay(1000L) print(".") } } jobs.forEach { it.join() } } Example Try that with 100k threads! Prints 100k dots after one second delay
  108. 108. fun main(args: Array<String>) = runBlocking<Unit> { val jobs = List(100_000) { launch { delay(1000L) print(".") } } jobs.forEach { it.join() } } Example
  109. 109. fun main(args: Array<String>) { val jobs = List(100_000) { thread { Thread.sleep(1000L) print(".") } } jobs.forEach { it.join() } } Example
  110. 110. Demo
  111. 111. fun main(args: Array<String>) { val jobs = List(100_000) { thread { Thread.sleep(1000L) print(".") } } jobs.forEach { it.join() } } Example Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
  112. 112. Java interop
  113. 113. CompletableFuture<Image> loadImageAsync(String name) { … }Java
  114. 114. CompletableFuture<Image> loadImageAsync(String name) { … } CompletableFuture<Image> loadAndCombineAsync(String name1, String name2) Imagine implementing it in Java… Java
  115. 115. CompletableFuture<Image> loadImageAsync(String name) { … } CompletableFuture<Image> loadAndCombineAsync(String name1, String name2) { CompletableFuture<Image> future1 = loadImageAsync(name1); CompletableFuture<Image> future2 = loadImageAsync(name2); return future1.thenCompose(image1 -> future2.thenCompose(image2 -> CompletableFuture.supplyAsync(() -> combineImages(image1, image2)))); } Java
  116. 116. CompletableFuture<Image> loadImageAsync(String name) { … } CompletableFuture<Image> loadAndCombineAsync(String name1, String name2) { CompletableFuture<Image> future1 = loadImageAsync(name1); CompletableFuture<Image> future2 = loadImageAsync(name2); return future1.thenCompose(image1 -> future2.thenCompose(image2 -> CompletableFuture.supplyAsync(() -> combineImages(image1, image2)))); } Java
  117. 117. CompletableFuture<Image> loadImageAsync(String name) { … } fun loadAndCombineAsync( name1: String, name2: String ): CompletableFuture<Image> = future { val future1 = loadImageAsync(name1) val future2 = loadImageAsync(name2) combineImages(future1.await(), future2.await()) } Kotlin Java
  118. 118. CompletableFuture<Image> loadImageAsync(String name) { … } fun loadAndCombineAsync( name1: String, name2: String ): CompletableFuture<Image> = future { val future1 = loadImageAsync(name1) val future2 = loadImageAsync(name2) combineImages(future1.await(), future2.await()) } Kotlin Java
  119. 119. CompletableFuture<Image> loadImageAsync(String name) { … } fun loadAndCombineAsync( name1: String, name2: String ): CompletableFuture<Image> = future { val future1 = loadImageAsync(name1) val future2 = loadImageAsync(name2) combineImages(future1.await(), future2.await()) } future coroutine builder Kotlin Java
  120. 120. CompletableFuture<Image> loadImageAsync(String name) { … } fun loadAndCombineAsync( name1: String, name2: String ): CompletableFuture<Image> = future { val future1 = loadImageAsync(name1) val future2 = loadImageAsync(name2) combineImages(future1.await(), future2.await()) } Kotlin Java
  121. 121. CompletableFuture<Image> loadImageAsync(String name) { … } fun loadAndCombineAsync( name1: String, name2: String ): CompletableFuture<Image> = future { val future1 = loadImageAsync(name1) val future2 = loadImageAsync(name2) combineImages(future1.await(), future2.await()) } Extension for Java’s CompletableFuture Kotlin Java
  122. 122. Beyond asynchronous code
  123. 123. Fibonacci sequence val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } }
  124. 124. val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } A coroutine builder with restricted suspension
  125. 125. val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } A suspending function
  126. 126. The same building blocks fun <T> buildSequence( builderAction: suspend SequenceBuilder<T>.() -> Unit ): Sequence<T> { … }
  127. 127. fun <T> buildSequence( builderAction: suspend SequenceBuilder<T>.() -> Unit ): Sequence<T> { … } Result is a synchronous sequence
  128. 128. fun <T> buildSequence( builderAction: suspend SequenceBuilder<T>.() -> Unit ): Sequence<T> { … } Suspending lambda with receiver
  129. 129. fun <T> buildSequence( builderAction: suspend SequenceBuilder<T>.() -> Unit ): Sequence<T> { … } @RestrictsSuspension abstract class SequenceBuilder<in T> { abstract suspend fun yield(value: T) } Coroutine is restricted only to suspending functions defined here
  130. 130. Synchronous val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } val iter = fibonacci.iterator()
  131. 131. val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } val iter = fibonacci.iterator() println(iter.next())
  132. 132. val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } val iter = fibonacci.iterator() println(iter.next())
  133. 133. val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } val iter = fibonacci.iterator() println(iter.next()) // 1
  134. 134. val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } val iter = fibonacci.iterator() println(iter.next()) // 1 println(iter.next())
  135. 135. val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } val iter = fibonacci.iterator() println(iter.next()) // 1 println(iter.next())
  136. 136. val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } val iter = fibonacci.iterator() println(iter.next()) // 1 println(iter.next()) // 1
  137. 137. val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } val iter = fibonacci.iterator() println(iter.next()) // 1 println(iter.next()) // 1 println(iter.next()) // 2
  138. 138. val fibonacci = buildSequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } val iter = fibonacci.iterator() println(iter.next()) // 1 println(iter.next()) // 1 println(iter.next()) // 2 Synchronous with invoker
  139. 139. Library vs Language
  140. 140. Classic async async/await generate/yield Keywords
  141. 141. Kotlin coroutines suspend Modifier
  142. 142. Kotlin coroutines Standard library
  143. 143. Kotlin coroutines Standard library kotlinx-coroutines launch, async, runBlocking, future, delay, Job, Deferred, etc http://github.com/kotlin/kotlinx.coroutines
  144. 144. Experimental status Coroutines are here to stay Backwards compatible inside 1.1 & 1.2 To be finalized in the future
  145. 145. #kotlinconf17 relizarov elizarov at JetBrains Roman Elizarov Thank you Any questions?

×