SlideShare a Scribd company logo
1 of 145
Download to read offline
Fresh	Async	With	Kotlin
Presented	at	QCon SF,	2017
/Roman	Elizarov	@	JetBrains
Speaker:	Roman	Elizarov
• 16+	years	experience
• Previously	developed	high-perf	trading	software	
@	Devexperts
• Teach	concurrent	&	distributed	programming	
@	St.	Petersburg	ITMO	University
• Chief	judge	
@	Northern	Eurasia	Contest	/	ACM	ICPC	
• Now	team	lead	in	Kotlin	Libraries	
@	JetBrains
Pragmatic.	Concise.	Modern.	Interoperable	with	Java.
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 received
}
1
fun requestToken(): Token { … }
fun createPost(token: Token, item: Item): Post {
// sends item to the server & waits
return post // returns resulting post
}
A	toy	problem
Kotlin
2
fun requestToken(): Token { … }
fun createPost(token: Token, item: Item): Post { … }
fun processPost(post: Post) {
// does some local processing of result
}
A	toy	problem
Kotlin
3
fun requestToken(): Token { … }
fun createPost(token: Token, item: Item): Post { … }
fun processPost(post: Post) { … }
A	toy	problem
Kotlin
fun postItem(item: Item) {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
1
2
3
Can	be	done	with	threads!
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?
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 received
}
1
Callbacks:	after
fun requestTokenAsync(cb: (Token) -> Unit) {
// makes request for a token, invokes callback when done
// returns immediately
}
1
callback
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
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)
}
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
Futures/Promises/Rx	
to	the	rescue
Sort	of	…
Futures:	before
fun requestTokenAsync(cb: (Token) -> Unit) {
// makes request for a token, invokes callback when done
// returns immediately
}
1
Futures:	after
fun requestTokenAsync(): Promise<Token> {
// makes request for a token
// returns promise for a future result immediately
}
1
future
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
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
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)
}
}
}
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
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…
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 result immediately
}
1
Coroutines:	after
suspend fun requestToken(): Token {
// makes request for a token & suspends
return token // returns result when received
}
1
natural	signature
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
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
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) }
}
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)
}
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
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
• 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().forEach { line ->
createPost(token, line.toItem())
}
• Custom	higher-order	functions
Bonus	features
val post = retryIO {
createPost(token, item)
}
Everything	like	in	blocking	code
How	does	it	work?
A	quick	peek	behind	the	scenes
Kotlin	suspending	functions
callback
Kotlin
Java/JVM
suspend fun createPost(token: Token, item: Item): Post { … }
Object createPost(Token token, Item item, Continuation<Post> cont) { … }
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<Post> cont) { … }
interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}
Kotlin	suspending	functions
callback
Kotlin
Java/JVM
suspend fun createPost(token: Token, item: Item): Post { … }
Object createPost(Token token, Item item, Continuation<Post> cont) { … }
interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}
Kotlin	suspending	functions
callback
Kotlin
Java/JVM
suspend fun createPost(token: Token, item: Item): Post { … }
Object createPost(Token token, Item item, Continuation<Post> cont) { … }
interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}
Kotlin	suspending	functions
callback
Kotlin
Java/JVM
suspend fun createPost(token: Token, item: Item): Post { … }
Object createPost(Token token, Item item, Continuation<Post> cont) { … }
interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}
Code	with	suspension	points
Kotlin
Java/JVM
Compiles	to	state	machine	
(simplified	code	shown)
val token = requestToken()
val post = createPost(token, item)
processPost(post)
switch (cont.label) {
case 0:
cont.label = 1;
requestToken(cont);
break;
case 1:
Token token = (Token) prevResult;
cont.label = 2;
createPost(token, item, cont);
break;
case 2:
Post post = (Post) prevResult;
processPost(post);
break;
}
Code	with	suspension	points
Kotlin
Java/JVM
val token = requestToken()
val post = createPost(token, item)
processPost(post)
switch (cont.label) {
case 0:
cont.label = 1;
requestToken(cont);
break;
case 1:
Token token = (Token) prevResult;
cont.label = 2;
createPost(token, item, cont);
break;
case 2:
Post post = (Post) prevResult;
processPost(post);
break;
}
Integration
Zoo	of	futures	on	JVM
interface Service {
fun createPost(token: Token, item: Item): Call<Post>
}
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
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
suspend fun <T> Call<T>.await(): T {
…
}
Callbacks	everywhere
suspend fun <T> Call<T>.await(): T {
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
// todo
}
override fun onFailure(call: Call<T>, t: Throwable) {
// todo
}
})
}
suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call<T>, t: Throwable) {
cont.resumeWithException(t)
}
})
}
suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T
suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T
suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T
Regular	function
Inspired	by	call/cc from	Scheme
suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call<T>, t: Throwable) {
cont.resumeWithException(t)
}
})
}
Install	callback
suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call<T>, t: Throwable) {
cont.resumeWithException(t)
}
})
}
Install	callback
suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call<T>, t: Throwable) {
cont.resumeWithException(t)
}
})
}
Analyze	response
suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call<T>, t: Throwable) {
cont.resumeWithException(t)
}
})
}
Analyze	response
suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful)
cont.resume(response.body()!!)
else
cont.resumeWithException(ErrorResponse(response))
}
override fun onFailure(call: Call<T>, t: Throwable) {
cont.resumeWithException(t)
}
})
}
That’s	all
Out-of-the	box	integrations
kotlinx-coroutines-core
jdk8
guava
nio
reactor
rx1
rx2
Coroutine	builders
How	can	we	start	a	coroutine?
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)
}
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)
}
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
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
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
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
Launch
fun postItem(item: Item) {
launch {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
}
coroutine	builder
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
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)
}
}
UI	Context
Just	specify	the	context
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
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
The	classic	approach
Kotlin-way
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) { … }
Kotlin
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)
async Task<Token> requestToken() { … }
async Task<Post> createPost(Token token, Item item) { … }
void processPost(Post post) { … }
C#
async Task postItem(Item item) {
var token = await requestToken();
var post = await createPost(token, item);
processPost(post);
}
Classic-way
mark	with	async
async Task<Token> requestToken() { … }
async Task<Post> createPost(Token token, Item item) { … }
void processPost(Post post) { … }
C#
async Task postItem(Item item) {
var token = await requestToken();
var post = await createPost(token, item);
processPost(post);
}
Classic-way
use	await	to	suspend
async Task<Token> requestToken() { … }
async Task<Post> createPost(Token token, Item item) { … }
void processPost(Post post) { … }
C#
async Task postItem(Item item) {
var token = await requestToken();
var post = await createPost(token, item);
processPost(post);
}
Classic-way
returns	a	future
async Task<Token> requestToken() { … }
async Task<Post> createPost(Token token, Item item) { … }
void processPost(Post post) { … }
C#
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
Concurrency	is	hard
Concurrency	has	to	be	explicit
Kotlin	suspending	functions	are	
designed	to	imitate	sequential behavior	
by	default	
Concurrency	is	hard
Concurrency	has	to	be	explicit
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> loadImageAsync(String name) { … }
Start	multiple	operations	
concurrently
C#
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#
Use-case	for	async
var result = combineImages(image1, image2);
var promise1 = loadImageAsync(name1);
var promise2 = loadImageAsync(name2);
var image1 = await promise1;
var image2 = await promise2;
async Task<Image> loadImageAsync(String name) { … }C#
Kotlin	async function
fun loadImageAsync(name: String): Deferred<Image> =
async { … }
Kotlin
Kotlin	async function
fun loadImageAsync(name: String): Deferred<Image> =
async { … }
A	regular	function
Kotlin
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)
val deferred2 = loadImageAsync(name2)
Start	multiple	operations	
concurrently
Kotlin
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
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
Using	async function	when	needed
suspend fun loadImage(name: String): Image { … }
Is	defined	as	suspending	function,	not	async
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())
}
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())
}
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())
}
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())
}
Kotlin approach	to	async
requestToken() VALID –>	produces	Token
async { requestToken() } VALID –>	produces	Deferred<Token>
sequential	behavior
concurrent	behavior
Kotlin
Kotlin
default
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.forEach { it.join() }
}
Example
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
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.forEach { it.join() }
}
Example
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.forEach { it.join() }
}
Example
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
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
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
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.forEach { it.join() }
}
Example
fun main(args: Array<String>) {
val jobs = List(100_000) {
thread {
Thread.sleep(1000L)
print(".")
}
}
jobs.forEach { it.join() }
}
Example
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
Java	interop
Can	we	use	Kotlin	coroutines	with	Java	code?
Java	interop
CompletableFuture<Image> loadImageAsync(String name) { … }Java
CompletableFuture<Image> loadImageAsync(String name) { … }
CompletableFuture<Image> loadAndCombineAsync(String name1,
String name2)
Java
Imagine	implementing	it	in	Java…
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
CompletableFuture<Image> loadImageAsync(String name) { … }Java
fun loadAndCombineAsync(
name1: String,
name2: String
): CompletableFuture<Image> =
…
Kotlin
CompletableFuture<Image> loadImageAsync(String name) { … }Java
fun loadAndCombineAsync(
name1: String,
name2: String
): CompletableFuture<Image> =
future {
val future1 = loadImageAsync(name1)
val future2 = loadImageAsync(name2)
combineImages(future1.await(), future2.await())
}
Kotlin
CompletableFuture<Image> loadImageAsync(String name) { … }Java
fun loadAndCombineAsync(
name1: String,
name2: String
): CompletableFuture<Image> =
future {
val future1 = loadImageAsync(name1)
val future2 = loadImageAsync(name2)
combineImages(future1.await(), future2.await())
}
Kotlin
future	coroutine	builder
CompletableFuture<Image> loadImageAsync(String name) { … }Java
fun loadAndCombineAsync(
name1: String,
name2: String
): CompletableFuture<Image> =
future {
val future1 = loadImageAsync(name1)
val future2 = loadImageAsync(name2)
combineImages(future1.await(), future2.await())
}
Kotlin
CompletableFuture<Image> loadImageAsync(String name) { … }Java
fun loadAndCombineAsync(
name1: String,
name2: String
): CompletableFuture<Image> =
future {
val future1 = loadImageAsync(name1)
val future2 = loadImageAsync(name2)
combineImages(future1.await(), future2.await())
}
Kotlin
Beyond	asynchronous	code
Kotlin’s approach	to	generate/yield	– synchronous	coroutines
Fibonacci	sequence
val fibonacci: Sequence<Int> = …
Fibonacci	sequence
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = tmp
}
}
Fibonacci	sequence
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
Fibonacci	sequence
val fibonacci = buildSequence {
var cur = 1
var next = 1
while (true) {
yield(cur)
val tmp = cur + next
cur = next
next = tmp
}
}
A	suspending	function
The	same	building	blocks
public fun <T> buildSequence(
builderAction: suspend SequenceBuilder<T>.() -> Unit
): Sequence<T> { … }
The	same	building	blocks
public fun <T> buildSequence(
builderAction: suspend SequenceBuilder<T>.() -> Unit
): Sequence<T> { … }
Result	is	a	synchronous sequence
The	same	building	blocks
public fun <T> buildSequence(
builderAction: suspend SequenceBuilder<T>.() -> Unit
): Sequence<T> { … }
Suspending	lambda	with	receiver
The	same	building	blocks
public 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
Library	vs	Language
Keeping	the	core	language	small
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://github.com/kotlin/kotlinx.coroutines
Experimental	in	Kotlin	1.1	&	1.2
Coroutines	can	be	used in	production
Backwards	compatible	inside	1.1	&	1.2
To	be	finalized	in	the	future
Thank	you
Any	questions?
Slides	are	available	at	www.slideshare.net/elizarov
email	me	to	elizarov at	gmail
relizarov

More Related Content

What's hot

How to Clone Flappy Bird in Swift
How to Clone Flappy Bird in SwiftHow to Clone Flappy Bird in Swift
How to Clone Flappy Bird in Swift
Giordano Scalzo
 
Better Software: introduction to good code
Better Software: introduction to good codeBetter Software: introduction to good code
Better Software: introduction to good code
Giordano Scalzo
 
Feel of Kotlin (Berlin JUG 16 Apr 2015)
Feel of Kotlin (Berlin JUG 16 Apr 2015)Feel of Kotlin (Berlin JUG 16 Apr 2015)
Feel of Kotlin (Berlin JUG 16 Apr 2015)
intelliyole
 

What's hot (20)

Kotlin coroutine - behind the scenes
Kotlin coroutine - behind the scenesKotlin coroutine - behind the scenes
Kotlin coroutine - behind the scenes
 
How to Clone Flappy Bird in Swift
How to Clone Flappy Bird in SwiftHow to Clone Flappy Bird in Swift
How to Clone Flappy Bird in Swift
 
Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?
 
Python Yield
Python YieldPython Yield
Python Yield
 
Goroutines and Channels in practice
Goroutines and Channels in practiceGoroutines and Channels in practice
Goroutines and Channels in practice
 
Better Software: introduction to good code
Better Software: introduction to good codeBetter Software: introduction to good code
Better Software: introduction to good code
 
Feel of Kotlin (Berlin JUG 16 Apr 2015)
Feel of Kotlin (Berlin JUG 16 Apr 2015)Feel of Kotlin (Berlin JUG 16 Apr 2015)
Feel of Kotlin (Berlin JUG 16 Apr 2015)
 
Go ahead, make my day
Go ahead, make my dayGo ahead, make my day
Go ahead, make my day
 
EcmaScript 6 - The future is here
EcmaScript 6 - The future is hereEcmaScript 6 - The future is here
EcmaScript 6 - The future is here
 
The future of async i/o in Python
The future of async i/o in PythonThe future of async i/o in Python
The future of async i/o in Python
 
Kotlin - Coroutine
Kotlin - CoroutineKotlin - Coroutine
Kotlin - Coroutine
 
D-Talk: What's awesome about Ruby 2.x and Rails 4
D-Talk: What's awesome about Ruby 2.x and Rails 4D-Talk: What's awesome about Ruby 2.x and Rails 4
D-Talk: What's awesome about Ruby 2.x and Rails 4
 
Don't do this
Don't do thisDon't do this
Don't do this
 
Should it be routine to use coroutines?
Should it be routine to use coroutines?Should it be routine to use coroutines?
Should it be routine to use coroutines?
 
Metaprogramming and Reflection in Common Lisp
Metaprogramming and Reflection in Common LispMetaprogramming and Reflection in Common Lisp
Metaprogramming and Reflection in Common Lisp
 
ES6 - Next Generation Javascript
ES6 - Next Generation JavascriptES6 - Next Generation Javascript
ES6 - Next Generation Javascript
 
Something about Golang
Something about GolangSomething about Golang
Something about Golang
 
ECMAScript 6
ECMAScript 6ECMAScript 6
ECMAScript 6
 
Swift 3 Programming for iOS : subscript init
Swift 3 Programming for iOS : subscript initSwift 3 Programming for iOS : subscript init
Swift 3 Programming for iOS : subscript init
 
ES2015 (ES6) Overview
ES2015 (ES6) OverviewES2015 (ES6) Overview
ES2015 (ES6) Overview
 

Similar to Fresh Async with Kotlin @ QConSF 2017

Similar to Fresh Async with Kotlin @ QConSF 2017 (20)

Fresh Async with Kotlin
Fresh Async with KotlinFresh Async with Kotlin
Fresh Async with Kotlin
 
Coroutines in Kotlin
Coroutines in KotlinCoroutines in Kotlin
Coroutines in Kotlin
 
Coroutines in Kotlin. In-depth review
Coroutines in Kotlin. In-depth reviewCoroutines in Kotlin. In-depth review
Coroutines in Kotlin. In-depth review
 
Dive into kotlins coroutines
Dive into kotlins coroutinesDive into kotlins coroutines
Dive into kotlins coroutines
 
TDC2018SP | Trilha Kotlin - Programacao assincrona utilizando Coroutines
TDC2018SP | Trilha Kotlin - Programacao assincrona utilizando CoroutinesTDC2018SP | Trilha Kotlin - Programacao assincrona utilizando Coroutines
TDC2018SP | Trilha Kotlin - Programacao assincrona utilizando Coroutines
 
Paradigma FP y OOP usando técnicas avanzadas de Programación | Programacion A...
Paradigma FP y OOP usando técnicas avanzadas de Programación | Programacion A...Paradigma FP y OOP usando técnicas avanzadas de Programación | Programacion A...
Paradigma FP y OOP usando técnicas avanzadas de Programación | Programacion A...
 
Concurrent programming with Celluloid (MWRC 2012)
Concurrent programming with Celluloid (MWRC 2012)Concurrent programming with Celluloid (MWRC 2012)
Concurrent programming with Celluloid (MWRC 2012)
 
Programação assíncrona utilizando Coroutines
Programação assíncrona utilizando CoroutinesProgramação assíncrona utilizando Coroutines
Programação assíncrona utilizando Coroutines
 
Non Blocking I/O for Everyone with RxJava
Non Blocking I/O for Everyone with RxJavaNon Blocking I/O for Everyone with RxJava
Non Blocking I/O for Everyone with RxJava
 
4Developers: Michał Szczepanik- Kotlin - Let’s ketchup it
4Developers: Michał Szczepanik- Kotlin - Let’s ketchup it4Developers: Michał Szczepanik- Kotlin - Let’s ketchup it
4Developers: Michał Szczepanik- Kotlin - Let’s ketchup it
 
Python Coroutines, Present and Future
Python Coroutines, Present and FuturePython Coroutines, Present and Future
Python Coroutines, Present and Future
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
 
JVMLS 2016. Coroutines in Kotlin
JVMLS 2016. Coroutines in KotlinJVMLS 2016. Coroutines in Kotlin
JVMLS 2016. Coroutines in Kotlin
 
Concurrency in Golang
Concurrency in GolangConcurrency in Golang
Concurrency in Golang
 
Kotlin boost yourproductivity
Kotlin boost yourproductivityKotlin boost yourproductivity
Kotlin boost yourproductivity
 
Kotlin for backend development (Hackaburg 2018 Regensburg)
Kotlin for backend development (Hackaburg 2018 Regensburg)Kotlin for backend development (Hackaburg 2018 Regensburg)
Kotlin for backend development (Hackaburg 2018 Regensburg)
 
Kotlin Coroutines - the new async
Kotlin Coroutines - the new asyncKotlin Coroutines - the new async
Kotlin Coroutines - the new async
 
C#을 이용한 task 병렬화와 비동기 패턴
C#을 이용한 task 병렬화와 비동기 패턴C#을 이용한 task 병렬화와 비동기 패턴
C#을 이용한 task 병렬화와 비동기 패턴
 
Refactor legacy code through pure functions
Refactor legacy code through pure functionsRefactor legacy code through pure functions
Refactor legacy code through pure functions
 
Kotlin for Android Developers - 3
Kotlin for Android Developers - 3Kotlin for Android Developers - 3
Kotlin for Android Developers - 3
 

More from Roman Elizarov

Многопоточное Программирование - Теория и Практика
Многопоточное Программирование - Теория и ПрактикаМногопоточное Программирование - Теория и Практика
Многопоточное Программирование - Теория и Практика
Roman Elizarov
 
Многопоточные Алгоритмы (для BitByte 2014)
Многопоточные Алгоритмы (для BitByte 2014)Многопоточные Алгоритмы (для BitByte 2014)
Многопоточные Алгоритмы (для BitByte 2014)
Roman Elizarov
 
Теоретический минимум для понимания Java Memory Model (для JPoint 2014)
Теоретический минимум для понимания Java Memory Model (для JPoint 2014)Теоретический минимум для понимания Java Memory Model (для JPoint 2014)
Теоретический минимум для понимания Java Memory Model (для JPoint 2014)
Roman Elizarov
 
ACM ICPC 2013 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2013 NEERC (Northeastern European Regional Contest) Problems ReviewACM ICPC 2013 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2013 NEERC (Northeastern European Regional Contest) Problems Review
Roman Elizarov
 
Java Serialization Facts and Fallacies
Java Serialization Facts and FallaciesJava Serialization Facts and Fallacies
Java Serialization Facts and Fallacies
Roman Elizarov
 
Millions quotes per second in pure java
Millions quotes per second in pure javaMillions quotes per second in pure java
Millions quotes per second in pure java
Roman Elizarov
 
ACM ICPC 2012 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2012 NEERC (Northeastern European Regional Contest) Problems ReviewACM ICPC 2012 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2012 NEERC (Northeastern European Regional Contest) Problems Review
Roman Elizarov
 
The theory of concurrent programming for a seasoned programmer
The theory of concurrent programming for a seasoned programmerThe theory of concurrent programming for a seasoned programmer
The theory of concurrent programming for a seasoned programmer
Roman Elizarov
 

More from Roman Elizarov (20)

Kotlin Coroutines in Practice @ KotlinConf 2018
Kotlin Coroutines in Practice @ KotlinConf 2018Kotlin Coroutines in Practice @ KotlinConf 2018
Kotlin Coroutines in Practice @ KotlinConf 2018
 
Scale Up with Lock-Free Algorithms @ JavaOne
Scale Up with Lock-Free Algorithms @ JavaOneScale Up with Lock-Free Algorithms @ JavaOne
Scale Up with Lock-Free Algorithms @ JavaOne
 
Lock-free algorithms for Kotlin Coroutines
Lock-free algorithms for Kotlin CoroutinesLock-free algorithms for Kotlin Coroutines
Lock-free algorithms for Kotlin Coroutines
 
Introduction to Kotlin coroutines
Introduction to Kotlin coroutinesIntroduction to Kotlin coroutines
Introduction to Kotlin coroutines
 
Non blocking programming and waiting
Non blocking programming and waitingNon blocking programming and waiting
Non blocking programming and waiting
 
ACM ICPC 2016 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2016 NEERC (Northeastern European Regional Contest) Problems ReviewACM ICPC 2016 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2016 NEERC (Northeastern European Regional Contest) Problems Review
 
Многопоточное Программирование - Теория и Практика
Многопоточное Программирование - Теория и ПрактикаМногопоточное Программирование - Теория и Практика
Многопоточное Программирование - Теория и Практика
 
Wait for your fortune without Blocking!
Wait for your fortune without Blocking!Wait for your fortune without Blocking!
Wait for your fortune without Blocking!
 
ACM ICPC 2015 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2015 NEERC (Northeastern European Regional Contest) Problems ReviewACM ICPC 2015 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2015 NEERC (Northeastern European Regional Contest) Problems Review
 
ACM ICPC 2014 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2014 NEERC (Northeastern European Regional Contest) Problems ReviewACM ICPC 2014 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2014 NEERC (Northeastern European Regional Contest) Problems Review
 
Why GC is eating all my CPU?
Why GC is eating all my CPU?Why GC is eating all my CPU?
Why GC is eating all my CPU?
 
Многопоточные Алгоритмы (для BitByte 2014)
Многопоточные Алгоритмы (для BitByte 2014)Многопоточные Алгоритмы (для BitByte 2014)
Многопоточные Алгоритмы (для BitByte 2014)
 
Теоретический минимум для понимания Java Memory Model (для JPoint 2014)
Теоретический минимум для понимания Java Memory Model (для JPoint 2014)Теоретический минимум для понимания Java Memory Model (для JPoint 2014)
Теоретический минимум для понимания Java Memory Model (для JPoint 2014)
 
DIY Java Profiling
DIY Java ProfilingDIY Java Profiling
DIY Java Profiling
 
ACM ICPC 2013 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2013 NEERC (Northeastern European Regional Contest) Problems ReviewACM ICPC 2013 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2013 NEERC (Northeastern European Regional Contest) Problems Review
 
Java Serialization Facts and Fallacies
Java Serialization Facts and FallaciesJava Serialization Facts and Fallacies
Java Serialization Facts and Fallacies
 
Millions quotes per second in pure java
Millions quotes per second in pure javaMillions quotes per second in pure java
Millions quotes per second in pure java
 
ACM ICPC 2012 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2012 NEERC (Northeastern European Regional Contest) Problems ReviewACM ICPC 2012 NEERC (Northeastern European Regional Contest) Problems Review
ACM ICPC 2012 NEERC (Northeastern European Regional Contest) Problems Review
 
The theory of concurrent programming for a seasoned programmer
The theory of concurrent programming for a seasoned programmerThe theory of concurrent programming for a seasoned programmer
The theory of concurrent programming for a seasoned programmer
 
Пишем самый быстрый хеш для кэширования данных
Пишем самый быстрый хеш для кэширования данныхПишем самый быстрый хеш для кэширования данных
Пишем самый быстрый хеш для кэширования данных
 

Recently uploaded

EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
Earley Information Science
 

Recently uploaded (20)

Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 

Fresh Async with Kotlin @ QConSF 2017