KEMBAR78
2019-01-29 - Demystifying Kotlin Coroutines | PPTX
training@instil.co
January 2019
© Instil Software 2019
Demystifying Kotlin Coroutines
GDG Dublin
@BoyleEamonn
Eamonn Boyle
Me
Us
Develop
Consult
Train
We’ve really bought into Kotlin
Kotlin Conf 2018
We held a workshop at the conference
• React Web App with Kotlin on Server & Browser
Kotlin Conf 2018
Kotlin Belfast User Group
Our Kotlin Courses
Our Kotlin Courses
47 Degrees
Coroutines
Long Story Short - Synchronous is Easy
fun doWork() {
disableUI()
val exchangeRates = readExchangeRate()
val report = buildReport(exchangeRates)
saveReport(report)
enableUI()
}
1
2
3
4
5
imdb.com
return
Long Story Short - Synchronous is Easy
fun doWork() {
disableUI()
val exchangeRates = readExchangeRate()
val report = buildReport(exchangeRates)
saveReport(report)
enableUI()
}
1
2
3
4
5
We can’t escape asynchronous programming
Typically we can’t block due to limited resources
But we want ways to hide the complexity of async
Long Story Short – Callback Hell
fun doWork() {
disableUI()
readExchangeRate { exchangeRate ->
buildReport(exchangeRate) { report ->
saveReport(report) {
enableUI()
}
}
}
}
imdb.com
return
Long Story Short – Futures Help
fun doWork() {
disableUI()
readExchangeRate()
.thenCompose(::buildReport)
.thenCompose(::saveReport)
.thenApply {
enableUI()
}
}
imdb.com
Once you’ve learned the
complete API, then you have my
permission to die
return
Long Story Short – Coroutines Are Better
suspend fun doWork() {
disableUI()
val exchangeRates = readExchangeRate()
val report = buildReport(exchangeRates)
saveReport(report)
enableUI()
}
imdb.com
suspend
suspend
suspend
return
Long Story Short – Coroutines Are Better
suspend fun doWork() {
disableUI()
val exchangeRates = readExchangeRate()
val report = buildReport(exchangeRates)
saveReport(report)
enableUI()
}
fun doWork() {
disableUI()
val exchangeRates = readExchangeRate()
val report = buildReport(exchangeRates)
saveReport(report)
enableUI()
}
Long Story Short – Control Dispatch
suspend fun doWorkWithThreadControl() {
disableUI()
withContext(Dispatchers.IO) {
val exchangeRates = readExchangeRate()
val report = buildReport(exchangeRates)
saveReport(report)
}
enableUI()
}
Generator Use Case
1 2 3 4 5 6 7 1 2 3 4 5 6 7
Reading
Processing
fun readData(filename: String): List<Int> {
val data = mutableListOf<Int>()
File(filename).useLines {lines ->
lines.forEach {
val datum = it.toInt()
log("Read $datum")
data.add(datum)
}
}
return data
}
fun processData(filename: String) {
for (datum in readData(filename)) {
log("Processing data $datum")
}
}
fun readData(filename: String): List<Int> {
val data = mutableListOf<Int>()
File(filename).useLines {lines ->
lines.forEach {
val datum = it.toInt()
log("Read $datum")
data.add(datum)
}
}
return data
}
fun processData(filename: String) {
val data = readData(filename)
for (datum in data) {
log("Processing data $datum")
}
}
fun readData(filename: String): List<Int> {
val data = mutableListOf<Int>()
File(filename).useLines {lines ->
lines.forEach {
val datum = it.toInt()
log("Read $datum")
data.add(datum)
}
}
return data
}
fun processData(filename: String) {
val data = readData(filename)
for (datum in data) {
log("Processing data $datum")
}
}
fun readData(filename: String): List<Int> {
val data = mutableListOf<Int>()
File(filename).useLines {lines ->
lines.forEach {
val datum = it.toInt()
log("Read $datum")
data.add(datum)
}
}
return data
}
fun processData(filename: String) {
val data = readData(filename)
for (datum in data) {
log("Processing data $datum")
}
}
What if the read takes too
long?
What if the amount of data
read is too large?
We may need to interleave reading and processing
How should we implement this? Perhaps hand code the interleaving
• Perhaps use standard interfaces to simplify
Generator Use Case
1 2 3 4 5 6 7 1 2 3 4 5 6 7
Reading Function
Processing Function
1 1 2 2 3 3 4 4 5 5 6 6 7 7
class CustomInterleaved(filename: String) {
val file = File(filename)
.inputStream()
.bufferedReader()
fun readData(): Int {
val line = file.readLine()
if (line == null) {
file.close()
return -1
}
val datum = line.toInt()
log("Read $datum")
return datum
}
}
Manual Interleaving
fun processData(filename: String) {
val interleaved = CustomInterleaved(filename)
while (true) {
val datum = interleaved.readData()
if (datum < 0) {
break
}
log("Processing data $datum")
}
}
class CustomInterleaved(filename: String) {
val file = File(filename)
.inputStream()
.bufferedReader()
fun readData(): Int {
val line = file.readLine()
if (line == null) {
file.close()
return -1
}
val datum = line.toInt()
log("Read $datum")
return datum
}
}
Manual Interleaving
fun processData(filename: String) {
val interleaved = CustomInterleaved(filename)
while (true) {
val datum = interleaved.readData()
if (datum < 0) {
break
}
log("Processing data $datum")
}
}
class CustomIterable(val filename: String) : Iterable<Int>, AutoCloseable {
val file = File(filename).inputStream().bufferedReader()
class CustomIterator(val file: BufferedReader) : Iterator<Int> {
override fun next(): Int {
val datum = file.readLine().toInt()
log("Read $datum")
return datum
}
override fun hasNext(): Boolean = file.ready()
}
override fun iterator(): Iterator<Int> = CustomIterator(file)
override fun close() = log("Closing file")
}
Standard Idioms – Iterable Interface
class CustomIterable(val filename: String) : Iterable<Int>, AutoCloseable {
val file = File(filename).inputStream().bufferedReader()
class CustomIterator(val file: BufferedReader) : Iterator<Int> {
override fun next(): Int {
val datum = file.readLine().toInt()
log("Read $datum")
return datum
}
override fun hasNext(): Boolean = file.ready()
}
override fun iterator(): Iterator<Int> = CustomIterator(file)
override fun close() = log("Closing file")
}
Standard Idioms – Iterable Interface
fun processData(filename: String) {
CustomIterable(filename).use {data ->
for (datum in data) {
log("Processing data $datum")
}
}
}
Benefit – Simpler Usage
fun readData(filename: String): List<Int> {
val data = mutableListOf<Int>()
File(filename).useLines {lines ->
lines.forEach {
val datum = it.toInt()
log("Read $datum")
data.add(datum)
}
}
return data
}
fun processData(filename: String) {
for (datum in readData(filename)) {
log("Processing data $datum")
}
}
fun readData(filename: String): Sequence<Int> {
return sequence {
File(filename).useLines {lines ->
lines.forEach {
val datum = it.toInt()
log("Read $datum")
yield(datum)
}
}
}
}
fun processData(filename: String) {
for (datum in readData(filename)) {
log("Processing data $datum")
}
}
Coroutine Solution
fun readData(filename: String): List<Int> {
val data = mutableListOf<Int>()
File(filename).useLines {lines ->
lines.forEach {
val datum = it.toInt()
log("Read $datum")
data.add(datum)
}
}
return data
}
Coroutine Solution
fun readData(filename: String): Sequence<Int> {
return sequence {
File(filename).useLines {lines ->
lines.forEach {
val datum = it.toInt()
log("Read $datum")
yield(datum)
}
}
}
} return
suspend
Let’s get technical
“Coroutines are computer-program components
that generalize subroutines for non-preemptive
multitasking, by allowing multiple entry points for
suspending and resuming execution at certain
locations.”
Wikipedia
Coroutine
1 1 2 2 3 3 4 4 5 5 6 6 7 7
1
1
2
2
3
3
4
4
5
5
6
6
7
7
“Coroutines simplify writing code that needs to
suspend and resume. e.g. generators,
asynchronous code, event loops etc
You write it like standard sequential code (more or
less) but under the hood it returns/suspends and
reenters/resumes. ”
Eamonn Boyle
Coroutine
..using Magic!!
They do not help us write faster code
• Although they can help us write more efficient systems
• This may make the whole system more responsive
It is easier to write as it is very similar to standard code
We get to use all the standard coding elements we are used to
• if, for, while
• try-catch
• function calls
• Etc
Benefits of Coroutines
Consider 3 independent jobs of work
Event Loop with Asynchronous Blocks
Start 5 6EndBlocking Start 5 6EndBlocking Start 5 6EndBlocking
fun workSync(message: String) {
log("$message started")
Thread.sleep(sleepTime)
log("$message ended")
}
jobs.forEach {
workSync(it)
}
[1]: Running Synchronous
[1]: Job 1 started
[1]: Job 1 ended
[1]: Job 2 started
[1]: Job 2 ended
[1]: Job 3 started
[1]: Job 3 ended
[1]: Synchronous took 15003 ms
Consider 3 independent jobs of work
Event Loop with Asynchronous Blocks
Start 5 6EndBlocking Start 5 6EndBlocking Start 5 6EndBlocking
Start 5 6EndBlocking
Start 5 6EndBlocking
Start 5 6EndBlocking
If we want to run these in parallel we could create multiple threads
jobs.map {
val thread = Thread {
workSync(it)
}
thread.start()
thread
}.forEach {
log("Joining thread ${it.id}")
it.join()
}
[1]: Running Parallel Threads
[1]: Joining with thread 12
[12]: Job 1 started
[14]: Job 3 started
[13]: Job 2 started
[13]: Job 2 ended
[12]: Job 1 ended
[14]: Job 3 ended
[1]: Joining with thread 13
[1]: Joining with thread 14
[1]: Parallel Threads took 5004 ms
There are scalability issues - threads are heavy
Startup times can be slow
The syntax is clunky
Optimal Usage of Fewer Threads
Start End
Blocking
Start End
Blocking
Start 5 6End
Blocking
Asynchronous Use Case – Thread Pools
Start End
Blocking
Start End
Blocking
Start 5 6End
Blocking
Asynchronous Use Case – Thread Dispatching
Start
EndBlocking
Start
EndBlocking
Start
5 6EndBlocking
Asynchronous Use Case – Common Characteristics
Start EndStart EndStart 5 6End
Quite often we must use certain threads for certain work
• E.g. UI typically has a single main thread
Quite often the asynchronous work can be optimised
• Event interrupts for I/O
• Single polling thread for multiple elements
• Pooling certain work on dedicated worker threads
fun mySleep(time: Long, action: () -> Unit): CompletableFuture<Unit> {
return CompletableFuture.runAsync {
Thread.sleep(time)
}.thenApply { action() }
}
fun workSync(message: String): CompletableFuture<Unit> {
log("$message started")
return mySleep(sleepTime) {
log("$message ended")
}
}
jobs.map {
workSync(it)
}.forEach {
it.join()
}
Callbacks & CompletableFuture Solution
fun mySleep(time: Long, action: () -> Unit): CompletableFuture<Unit> {
return CompletableFuture.runAsync {
Thread.sleep(time)
}.thenApply { action() }
}
fun workSync(message: String): CompletableFuture<Unit> {
log("$message started")
return mySleep(sleepTime) {
log("$message ended")
}
}
jobs.map {
workSync(it)
}.forEach {
it.join()
}
[1]: Running Synchronous
[1]: Job 1 started
[1]: Job 2 started
[1]: Job 3 started
[12]: Job 1 ended
[13]: Job 2 ended
[14]: Job 3 ended
[1]: Synchronous took 5018 ms
CompletableFuture Solution
CompletableFuture Solution
Start
EndBlocking
Start
EndBlocking
Start
5 6EndBlocking
fun workSync(message: String) {
log("$message started")
Thread.sleep(sleepTime)
log("$message ended")
}
jobs.forEach {
workSync(it)
}
Coroutine Solution
suspend fun workAsync(message: String) {
log("$message started")
delay(sleepTime)
log("$message ended")
}
runBlocking {
jobs.map {
launch {
workAsync(it)
}
}.joinAll()
}
Coroutine Solution
suspend fun workAsync(message: String) {
log("$message started")
delay(sleepTime)
log("$message ended")
}
runBlocking {
jobs.map {
launch {
workAsync(it)
}
}.joinAll()
}
[1]: Running Coroutines
[1]: Job 1 started
[1]: Job 2 started
[1]: Job 3 started
[1]: Job 1 ended
[1]: Job 2 ended
[1]: Job 3 ended
[1]: Coroutines took 5161 ms
Asynchronous Coroutines
Start End
Blocking
Start End
Blocking
Start 5 6End
Blocking
fun workSync(message: String) {
log("$message started")
Thread.sleep(sleepTime)
log("$message ended")
}
suspend fun workAsync(message: String) {
log("$message started")
delay(sleepTime)
log("$message ended")
}
Suspend Functions
fun workSync(message: String) {
log("$message started")
Thread.sleep(sleepTime)
log("$message ended")
}
Suspend Functions
suspend fun workAsync(message: String) {
log("$message started")
delay(sleepTime)
log("$message ended")
}
fun workSync(message: String) {
log("$message started")
Thread.sleep(sleepTime)
log("$message ended")
}
Suspend Functions
suspend fun workAsync(message: String) {
log("$message started")
delay(sleepTime)
log("$message ended")
}
This is a suspension
point
Asynchronous Coroutines
Start End
Blocking
Start End
Blocking
Start 5 6End
Blocking
Coroutines as Light Weight Threads
Start EndBlocking
Start EndBlocking
Start 5 6EndBlocking
Coroutines Scale
Start EndBlocking
Start EndBlocking
Start 5 6EndBlocking
Start EndBlocking
Start EndBlocking
Start 5 6EndBlocking
Start EndBlocking
Asynchronous Coroutines
Start End
Blocking
Start End
Blocking
Start 5 6End
Blocking
Asynchronous Coroutines
Start End
Blocking
Start End
Blocking
Start 5 6End
Blocking
suspend fun doWork() {
val a = step1()
step2()
val b = step3()
val total = a + b
log("The sum is $total")
}
Another Example
suspend fun doWork() {
val a = step1()
step2()
val b = step3()
val total = a + b
log("The sum is $total")
}
fun doWork(): CompletableFuture<Unit> {
return step1().thenCompose { a ->
step2().thenCompose {
step3().thenApply { b ->
val total = a + b
log("The sum is $total")
}
}
}
}
Coroutine as Completable
suspend fun doWork() {
val a = step1()
step2()
val b = step3()
val total = a + b
log("The sum is $total")
}
class SimpleCouroutineDoWork {
var label = 0
var a: Int? = null
var b: Int? = null
var total: Int? = null
fun resumeWith(...) {
}
}
Coroutine Implementation – State Machine
This class is the continuation
It represents the state of a suspending function
Coroutine Implementation – State Machine
suspend fun doWork() {
val a = step1()
step2()
val b = step3()
val total = a + b
log("The sum is $total")
}
fun resumeWith(result: Result<Any>) {
var newResult = result.getOrThrow()
if (label == 0) {
newResult = step1(this)
label = 1
if (newResult == COROUTINE_SUSPENDED) return
}
if (label == 1) {
a = newResult as Int
newResult = step2(this)
label = 2
if (newResult == COROUTINE_SUSPENDED) return
}
if (label == 2) {
b = newResult as Int
newResult = step3(this)
label = 3
if (newResult == COROUTINE_SUSPENDED) return
}
if (label == 4) {
total = a!! + b!!
log("The sum is $total")
}
}
suspend fun doWork() {
val a = step1()
step2()
val b = step3()
val total = a + b
log("The sum is $total")
}
fun resumeWith(result: Result<Any>) {
var newResult = result.getOrThrow()
if (label == 0) {
newResult = step1(this)
label = 1
if (newResult == COROUTINE_SUSPENDED) {
return
}
}
if (label == 1) {
a = newResult as Int
newResult = step2(this)
...
This is mainly for efficiency reasons
Less objects created to handle the context switching
Coroutine Implementation – State Machine
suspend fun doWork() {
val a = step1()
step2()
val b = step3()
val total = a + b
log("The sum is $total")
}
suspend fun step1() {
log("Step 1 started")
delay(1000)
log("Step 1 ended")
}
Coroutine – Suspend Functions Calling Each Other
Note, a suspend function is not a coroutine
• They are used to write parts of a coroutine
• They cannot be called from normal functions
A corountine is suspendable, sequential computation
It is similar to a thread but is not bound to any particular thread
• It can be created, started and ended
• It can suspend on one thread and resume on another
• It can return a result
Coroutine Builders
The compiler translates our suspend functions
The standard library provides only a primitive API
• This is very much by design
Using these fundamental building blocks, libraries add functionality
This add a lot of flexibility
• Consider different platforms – JVM, JS, Native
• Consider different use cases – generators, async await, actors
• We can add various different threading strategies
Coroutine APIs
© Instil Software 2019
• Practical Coroutine Usage
kotlinx.corountines
kotlinx.coroutines adds the API entitles for useful coroutines
Scope – Encloses a coroutines and its child coroutines
Context – A key/value collection of values to control execution
Dispatcher – Controls coroutine execution e.g. which threads to use
Builders – functions to create a coroutine
Interceptor – Wraps/intercepts coroutine execution
Bridge – A builder that can be called from a non-suspend function
Coroutine Builders
Controls which thread is used to execute a continuation
• A scope with its context will define a dispatcher
These will be platform dependent but exists in Dispatchers object
Coroutine Dispatchers
Dispatcher Description
Default Executes on a default background thread pool
Unconfined Executes using any available thread
Main Executes on the Main (UI) thread
IO Executes blocking I/O calls. Shares threads with Default
but will create and shutdown additional threads
Any standard Java Executor Service can become a dispatcher
• Simply invoke the ‘asCoroutineDispatcher’ extension method
Other dedicated functions exist but these are going to be replaced
Using a Custom Coroutine Dispatcher
fun runDemoViaCustomThreadPool(name: String) {
val threadPool = Executors.newFixedThreadPool(2)
val dispatcher = threadPool.asCoroutineDispatcher()
runBlocking(dispatcher) {
// ...
}
threadPool.shutdown()
}
These are functions that are supplied a suspend function/lambda
and creates a coroutine
Coroutine Builders
Builder Description
launch Returns a Job object representing the coroutine but is largely fire
and forget. It is synchronised with another coroutine using join.
async Returns a Deferred<T> object which is an extension of Job that
includes a result. The result is acquired using await.
runBlocking Runs a coroutine and blocks the current thread until it completes
sequence Creates generators using a suspend function
These suspending functions execute against a Coroutine Scope
• They are extension methods of CoroutineScope
Suspending Functions
Builder Description
delay Executes a non-blocking sleep
yield Yields the thread in single thread disaptchers
withContext Like runBlocking within a coroutine i.e. it suspends
withTimeout Sets a time limit on execution of a function
await, awaitAll Waits for one or multiple Deferred to complete and returns
the result/s
join, joinAll Waits for one or multiple jobs to complete
class MyActivity : AppCompatActivity(), CoroutineScope {
lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
override fun onDestroy() {
super.onDestroy()
job.cancel() // Cancel job on activity destroy. After Destroy
// all children jobs will be cancelled automatically
}
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/
Android Activity
class MyActivity : AppCompatActivity(), CoroutineScope {
lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
override fun onDestroy() {
super.onDestroy()
job.cancel() // Cancel job on activity destroy. After Destroy
// all children jobs will be cancelled automatically
}
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/
Android Activity
fun loadDataFromUI() = launch { // this (Main) Dispatcher
val ioData = async(Dispatchers.IO) { // IO Dispatcher
// blocking I/O operation
}
// Work here will execute in parallel to IO
// It will run on the Main thread
val data = ioData.await() // Wait for result of I/O
// Complete work in Main thread
}
Android Activity
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/
fun voidPayment(tender: CheckTender) = launch(Dispatchers.Default) {
when (tender.type) {
CREDIT, MANUAL_CREDIT -> voidCreditPayment(tender)
GIFT_CARD, MANUAL_GIFT_CARD -> voidGiftCardPayment(tender)
else -> onVoidFailed()
}
}
suspend fun voidCreditPayment(tender: CheckTender) {
try {
val voidPaymentResponse = voidService.voidPayment().await()
when (voidPaymentResponse.result) {
SUCCESS -> onCreditVoidSuccess(tender, voidPaymentResponse)
else -> onCreditVoidFailed(tender, voidPaymentResponse)
}
} catch (throwable: Throwable) {
onVoidFailed()
}
}
Coroutines went stable in 1.3
Similar things exist in other languages
• E.g. async await has been in C# since 2014, F# since 2012
• JavaScript, Python have very similar syntax to C#
• Golang has goroutines & channels
• They are all very useful at simplifying your code
We now have a great solution for the JVM (and others)
The Kotlin solution is built from primitives that open up flexibility
Summary
Questions?
https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md
https://github.com/Kotlin/kotlinx.coroutines
https://www.imdb.com
References

2019-01-29 - Demystifying Kotlin Coroutines

  • 1.
    training@instil.co January 2019 © InstilSoftware 2019 Demystifying Kotlin Coroutines GDG Dublin @BoyleEamonn Eamonn Boyle
  • 2.
  • 3.
  • 4.
  • 5.
    We’ve really boughtinto Kotlin Kotlin Conf 2018
  • 6.
    We held aworkshop at the conference • React Web App with Kotlin on Server & Browser Kotlin Conf 2018
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
    Long Story Short- Synchronous is Easy fun doWork() { disableUI() val exchangeRates = readExchangeRate() val report = buildReport(exchangeRates) saveReport(report) enableUI() } 1 2 3 4 5 imdb.com return
  • 13.
    Long Story Short- Synchronous is Easy fun doWork() { disableUI() val exchangeRates = readExchangeRate() val report = buildReport(exchangeRates) saveReport(report) enableUI() } 1 2 3 4 5 We can’t escape asynchronous programming Typically we can’t block due to limited resources But we want ways to hide the complexity of async
  • 14.
    Long Story Short– Callback Hell fun doWork() { disableUI() readExchangeRate { exchangeRate -> buildReport(exchangeRate) { report -> saveReport(report) { enableUI() } } } } imdb.com return
  • 15.
    Long Story Short– Futures Help fun doWork() { disableUI() readExchangeRate() .thenCompose(::buildReport) .thenCompose(::saveReport) .thenApply { enableUI() } } imdb.com Once you’ve learned the complete API, then you have my permission to die return
  • 16.
    Long Story Short– Coroutines Are Better suspend fun doWork() { disableUI() val exchangeRates = readExchangeRate() val report = buildReport(exchangeRates) saveReport(report) enableUI() } imdb.com suspend suspend suspend return
  • 17.
    Long Story Short– Coroutines Are Better suspend fun doWork() { disableUI() val exchangeRates = readExchangeRate() val report = buildReport(exchangeRates) saveReport(report) enableUI() } fun doWork() { disableUI() val exchangeRates = readExchangeRate() val report = buildReport(exchangeRates) saveReport(report) enableUI() }
  • 18.
    Long Story Short– Control Dispatch suspend fun doWorkWithThreadControl() { disableUI() withContext(Dispatchers.IO) { val exchangeRates = readExchangeRate() val report = buildReport(exchangeRates) saveReport(report) } enableUI() }
  • 20.
    Generator Use Case 12 3 4 5 6 7 1 2 3 4 5 6 7 Reading Processing fun readData(filename: String): List<Int> { val data = mutableListOf<Int>() File(filename).useLines {lines -> lines.forEach { val datum = it.toInt() log("Read $datum") data.add(datum) } } return data } fun processData(filename: String) { for (datum in readData(filename)) { log("Processing data $datum") } }
  • 21.
    fun readData(filename: String):List<Int> { val data = mutableListOf<Int>() File(filename).useLines {lines -> lines.forEach { val datum = it.toInt() log("Read $datum") data.add(datum) } } return data } fun processData(filename: String) { val data = readData(filename) for (datum in data) { log("Processing data $datum") } }
  • 22.
    fun readData(filename: String):List<Int> { val data = mutableListOf<Int>() File(filename).useLines {lines -> lines.forEach { val datum = it.toInt() log("Read $datum") data.add(datum) } } return data } fun processData(filename: String) { val data = readData(filename) for (datum in data) { log("Processing data $datum") } }
  • 23.
    fun readData(filename: String):List<Int> { val data = mutableListOf<Int>() File(filename).useLines {lines -> lines.forEach { val datum = it.toInt() log("Read $datum") data.add(datum) } } return data } fun processData(filename: String) { val data = readData(filename) for (datum in data) { log("Processing data $datum") } } What if the read takes too long? What if the amount of data read is too large?
  • 24.
    We may needto interleave reading and processing How should we implement this? Perhaps hand code the interleaving • Perhaps use standard interfaces to simplify Generator Use Case 1 2 3 4 5 6 7 1 2 3 4 5 6 7 Reading Function Processing Function 1 1 2 2 3 3 4 4 5 5 6 6 7 7
  • 25.
    class CustomInterleaved(filename: String){ val file = File(filename) .inputStream() .bufferedReader() fun readData(): Int { val line = file.readLine() if (line == null) { file.close() return -1 } val datum = line.toInt() log("Read $datum") return datum } } Manual Interleaving fun processData(filename: String) { val interleaved = CustomInterleaved(filename) while (true) { val datum = interleaved.readData() if (datum < 0) { break } log("Processing data $datum") } }
  • 26.
    class CustomInterleaved(filename: String){ val file = File(filename) .inputStream() .bufferedReader() fun readData(): Int { val line = file.readLine() if (line == null) { file.close() return -1 } val datum = line.toInt() log("Read $datum") return datum } } Manual Interleaving fun processData(filename: String) { val interleaved = CustomInterleaved(filename) while (true) { val datum = interleaved.readData() if (datum < 0) { break } log("Processing data $datum") } }
  • 27.
    class CustomIterable(val filename:String) : Iterable<Int>, AutoCloseable { val file = File(filename).inputStream().bufferedReader() class CustomIterator(val file: BufferedReader) : Iterator<Int> { override fun next(): Int { val datum = file.readLine().toInt() log("Read $datum") return datum } override fun hasNext(): Boolean = file.ready() } override fun iterator(): Iterator<Int> = CustomIterator(file) override fun close() = log("Closing file") } Standard Idioms – Iterable Interface
  • 28.
    class CustomIterable(val filename:String) : Iterable<Int>, AutoCloseable { val file = File(filename).inputStream().bufferedReader() class CustomIterator(val file: BufferedReader) : Iterator<Int> { override fun next(): Int { val datum = file.readLine().toInt() log("Read $datum") return datum } override fun hasNext(): Boolean = file.ready() } override fun iterator(): Iterator<Int> = CustomIterator(file) override fun close() = log("Closing file") } Standard Idioms – Iterable Interface
  • 29.
    fun processData(filename: String){ CustomIterable(filename).use {data -> for (datum in data) { log("Processing data $datum") } } } Benefit – Simpler Usage
  • 30.
    fun readData(filename: String):List<Int> { val data = mutableListOf<Int>() File(filename).useLines {lines -> lines.forEach { val datum = it.toInt() log("Read $datum") data.add(datum) } } return data } fun processData(filename: String) { for (datum in readData(filename)) { log("Processing data $datum") } } fun readData(filename: String): Sequence<Int> { return sequence { File(filename).useLines {lines -> lines.forEach { val datum = it.toInt() log("Read $datum") yield(datum) } } } } fun processData(filename: String) { for (datum in readData(filename)) { log("Processing data $datum") } } Coroutine Solution
  • 31.
    fun readData(filename: String):List<Int> { val data = mutableListOf<Int>() File(filename).useLines {lines -> lines.forEach { val datum = it.toInt() log("Read $datum") data.add(datum) } } return data } Coroutine Solution fun readData(filename: String): Sequence<Int> { return sequence { File(filename).useLines {lines -> lines.forEach { val datum = it.toInt() log("Read $datum") yield(datum) } } } } return suspend
  • 32.
  • 33.
    “Coroutines are computer-programcomponents that generalize subroutines for non-preemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations.” Wikipedia Coroutine
  • 34.
    1 1 22 3 3 4 4 5 5 6 6 7 7
  • 35.
  • 36.
    “Coroutines simplify writingcode that needs to suspend and resume. e.g. generators, asynchronous code, event loops etc You write it like standard sequential code (more or less) but under the hood it returns/suspends and reenters/resumes. ” Eamonn Boyle Coroutine ..using Magic!!
  • 37.
    They do nothelp us write faster code • Although they can help us write more efficient systems • This may make the whole system more responsive It is easier to write as it is very similar to standard code We get to use all the standard coding elements we are used to • if, for, while • try-catch • function calls • Etc Benefits of Coroutines
  • 38.
    Consider 3 independentjobs of work Event Loop with Asynchronous Blocks Start 5 6EndBlocking Start 5 6EndBlocking Start 5 6EndBlocking fun workSync(message: String) { log("$message started") Thread.sleep(sleepTime) log("$message ended") } jobs.forEach { workSync(it) } [1]: Running Synchronous [1]: Job 1 started [1]: Job 1 ended [1]: Job 2 started [1]: Job 2 ended [1]: Job 3 started [1]: Job 3 ended [1]: Synchronous took 15003 ms
  • 39.
    Consider 3 independentjobs of work Event Loop with Asynchronous Blocks Start 5 6EndBlocking Start 5 6EndBlocking Start 5 6EndBlocking Start 5 6EndBlocking Start 5 6EndBlocking Start 5 6EndBlocking If we want to run these in parallel we could create multiple threads
  • 40.
    jobs.map { val thread= Thread { workSync(it) } thread.start() thread }.forEach { log("Joining thread ${it.id}") it.join() } [1]: Running Parallel Threads [1]: Joining with thread 12 [12]: Job 1 started [14]: Job 3 started [13]: Job 2 started [13]: Job 2 ended [12]: Job 1 ended [14]: Job 3 ended [1]: Joining with thread 13 [1]: Joining with thread 14 [1]: Parallel Threads took 5004 ms There are scalability issues - threads are heavy Startup times can be slow The syntax is clunky
  • 41.
    Optimal Usage ofFewer Threads Start End Blocking Start End Blocking Start 5 6End Blocking
  • 42.
    Asynchronous Use Case– Thread Pools Start End Blocking Start End Blocking Start 5 6End Blocking
  • 43.
    Asynchronous Use Case– Thread Dispatching Start EndBlocking Start EndBlocking Start 5 6EndBlocking
  • 44.
    Asynchronous Use Case– Common Characteristics Start EndStart EndStart 5 6End Quite often we must use certain threads for certain work • E.g. UI typically has a single main thread Quite often the asynchronous work can be optimised • Event interrupts for I/O • Single polling thread for multiple elements • Pooling certain work on dedicated worker threads
  • 45.
    fun mySleep(time: Long,action: () -> Unit): CompletableFuture<Unit> { return CompletableFuture.runAsync { Thread.sleep(time) }.thenApply { action() } } fun workSync(message: String): CompletableFuture<Unit> { log("$message started") return mySleep(sleepTime) { log("$message ended") } } jobs.map { workSync(it) }.forEach { it.join() } Callbacks & CompletableFuture Solution
  • 46.
    fun mySleep(time: Long,action: () -> Unit): CompletableFuture<Unit> { return CompletableFuture.runAsync { Thread.sleep(time) }.thenApply { action() } } fun workSync(message: String): CompletableFuture<Unit> { log("$message started") return mySleep(sleepTime) { log("$message ended") } } jobs.map { workSync(it) }.forEach { it.join() } [1]: Running Synchronous [1]: Job 1 started [1]: Job 2 started [1]: Job 3 started [12]: Job 1 ended [13]: Job 2 ended [14]: Job 3 ended [1]: Synchronous took 5018 ms CompletableFuture Solution
  • 47.
  • 48.
    fun workSync(message: String){ log("$message started") Thread.sleep(sleepTime) log("$message ended") } jobs.forEach { workSync(it) } Coroutine Solution suspend fun workAsync(message: String) { log("$message started") delay(sleepTime) log("$message ended") } runBlocking { jobs.map { launch { workAsync(it) } }.joinAll() }
  • 49.
    Coroutine Solution suspend funworkAsync(message: String) { log("$message started") delay(sleepTime) log("$message ended") } runBlocking { jobs.map { launch { workAsync(it) } }.joinAll() } [1]: Running Coroutines [1]: Job 1 started [1]: Job 2 started [1]: Job 3 started [1]: Job 1 ended [1]: Job 2 ended [1]: Job 3 ended [1]: Coroutines took 5161 ms
  • 50.
    Asynchronous Coroutines Start End Blocking StartEnd Blocking Start 5 6End Blocking
  • 51.
    fun workSync(message: String){ log("$message started") Thread.sleep(sleepTime) log("$message ended") } suspend fun workAsync(message: String) { log("$message started") delay(sleepTime) log("$message ended") } Suspend Functions
  • 52.
    fun workSync(message: String){ log("$message started") Thread.sleep(sleepTime) log("$message ended") } Suspend Functions suspend fun workAsync(message: String) { log("$message started") delay(sleepTime) log("$message ended") }
  • 53.
    fun workSync(message: String){ log("$message started") Thread.sleep(sleepTime) log("$message ended") } Suspend Functions suspend fun workAsync(message: String) { log("$message started") delay(sleepTime) log("$message ended") } This is a suspension point
  • 54.
    Asynchronous Coroutines Start End Blocking StartEnd Blocking Start 5 6End Blocking
  • 55.
    Coroutines as LightWeight Threads Start EndBlocking Start EndBlocking Start 5 6EndBlocking
  • 56.
    Coroutines Scale Start EndBlocking StartEndBlocking Start 5 6EndBlocking Start EndBlocking Start EndBlocking Start 5 6EndBlocking Start EndBlocking
  • 58.
    Asynchronous Coroutines Start End Blocking StartEnd Blocking Start 5 6End Blocking
  • 59.
    Asynchronous Coroutines Start End Blocking StartEnd Blocking Start 5 6End Blocking
  • 60.
    suspend fun doWork(){ val a = step1() step2() val b = step3() val total = a + b log("The sum is $total") } Another Example
  • 61.
    suspend fun doWork(){ val a = step1() step2() val b = step3() val total = a + b log("The sum is $total") } fun doWork(): CompletableFuture<Unit> { return step1().thenCompose { a -> step2().thenCompose { step3().thenApply { b -> val total = a + b log("The sum is $total") } } } } Coroutine as Completable
  • 62.
    suspend fun doWork(){ val a = step1() step2() val b = step3() val total = a + b log("The sum is $total") } class SimpleCouroutineDoWork { var label = 0 var a: Int? = null var b: Int? = null var total: Int? = null fun resumeWith(...) { } } Coroutine Implementation – State Machine This class is the continuation It represents the state of a suspending function
  • 63.
    Coroutine Implementation –State Machine suspend fun doWork() { val a = step1() step2() val b = step3() val total = a + b log("The sum is $total") } fun resumeWith(result: Result<Any>) { var newResult = result.getOrThrow() if (label == 0) { newResult = step1(this) label = 1 if (newResult == COROUTINE_SUSPENDED) return } if (label == 1) { a = newResult as Int newResult = step2(this) label = 2 if (newResult == COROUTINE_SUSPENDED) return } if (label == 2) { b = newResult as Int newResult = step3(this) label = 3 if (newResult == COROUTINE_SUSPENDED) return } if (label == 4) { total = a!! + b!! log("The sum is $total") } }
  • 64.
    suspend fun doWork(){ val a = step1() step2() val b = step3() val total = a + b log("The sum is $total") } fun resumeWith(result: Result<Any>) { var newResult = result.getOrThrow() if (label == 0) { newResult = step1(this) label = 1 if (newResult == COROUTINE_SUSPENDED) { return } } if (label == 1) { a = newResult as Int newResult = step2(this) ... This is mainly for efficiency reasons Less objects created to handle the context switching Coroutine Implementation – State Machine
  • 65.
    suspend fun doWork(){ val a = step1() step2() val b = step3() val total = a + b log("The sum is $total") } suspend fun step1() { log("Step 1 started") delay(1000) log("Step 1 ended") } Coroutine – Suspend Functions Calling Each Other
  • 66.
    Note, a suspendfunction is not a coroutine • They are used to write parts of a coroutine • They cannot be called from normal functions A corountine is suspendable, sequential computation It is similar to a thread but is not bound to any particular thread • It can be created, started and ended • It can suspend on one thread and resume on another • It can return a result Coroutine Builders
  • 67.
    The compiler translatesour suspend functions The standard library provides only a primitive API • This is very much by design Using these fundamental building blocks, libraries add functionality This add a lot of flexibility • Consider different platforms – JVM, JS, Native • Consider different use cases – generators, async await, actors • We can add various different threading strategies Coroutine APIs
  • 68.
    © Instil Software2019 • Practical Coroutine Usage kotlinx.corountines
  • 69.
    kotlinx.coroutines adds theAPI entitles for useful coroutines Scope – Encloses a coroutines and its child coroutines Context – A key/value collection of values to control execution Dispatcher – Controls coroutine execution e.g. which threads to use Builders – functions to create a coroutine Interceptor – Wraps/intercepts coroutine execution Bridge – A builder that can be called from a non-suspend function Coroutine Builders
  • 70.
    Controls which threadis used to execute a continuation • A scope with its context will define a dispatcher These will be platform dependent but exists in Dispatchers object Coroutine Dispatchers Dispatcher Description Default Executes on a default background thread pool Unconfined Executes using any available thread Main Executes on the Main (UI) thread IO Executes blocking I/O calls. Shares threads with Default but will create and shutdown additional threads
  • 71.
    Any standard JavaExecutor Service can become a dispatcher • Simply invoke the ‘asCoroutineDispatcher’ extension method Other dedicated functions exist but these are going to be replaced Using a Custom Coroutine Dispatcher fun runDemoViaCustomThreadPool(name: String) { val threadPool = Executors.newFixedThreadPool(2) val dispatcher = threadPool.asCoroutineDispatcher() runBlocking(dispatcher) { // ... } threadPool.shutdown() }
  • 72.
    These are functionsthat are supplied a suspend function/lambda and creates a coroutine Coroutine Builders Builder Description launch Returns a Job object representing the coroutine but is largely fire and forget. It is synchronised with another coroutine using join. async Returns a Deferred<T> object which is an extension of Job that includes a result. The result is acquired using await. runBlocking Runs a coroutine and blocks the current thread until it completes sequence Creates generators using a suspend function
  • 73.
    These suspending functionsexecute against a Coroutine Scope • They are extension methods of CoroutineScope Suspending Functions Builder Description delay Executes a non-blocking sleep yield Yields the thread in single thread disaptchers withContext Like runBlocking within a coroutine i.e. it suspends withTimeout Sets a time limit on execution of a function await, awaitAll Waits for one or multiple Deferred to complete and returns the result/s join, joinAll Waits for one or multiple jobs to complete
  • 74.
    class MyActivity :AppCompatActivity(), CoroutineScope { lateinit var job: Job override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) job = Job() } override fun onDestroy() { super.onDestroy() job.cancel() // Cancel job on activity destroy. After Destroy // all children jobs will be cancelled automatically } https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/ Android Activity
  • 75.
    class MyActivity :AppCompatActivity(), CoroutineScope { lateinit var job: Job override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) job = Job() } override fun onDestroy() { super.onDestroy() job.cancel() // Cancel job on activity destroy. After Destroy // all children jobs will be cancelled automatically } https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/ Android Activity
  • 76.
    fun loadDataFromUI() =launch { // this (Main) Dispatcher val ioData = async(Dispatchers.IO) { // IO Dispatcher // blocking I/O operation } // Work here will execute in parallel to IO // It will run on the Main thread val data = ioData.await() // Wait for result of I/O // Complete work in Main thread } Android Activity https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/
  • 77.
    fun voidPayment(tender: CheckTender)= launch(Dispatchers.Default) { when (tender.type) { CREDIT, MANUAL_CREDIT -> voidCreditPayment(tender) GIFT_CARD, MANUAL_GIFT_CARD -> voidGiftCardPayment(tender) else -> onVoidFailed() } } suspend fun voidCreditPayment(tender: CheckTender) { try { val voidPaymentResponse = voidService.voidPayment().await() when (voidPaymentResponse.result) { SUCCESS -> onCreditVoidSuccess(tender, voidPaymentResponse) else -> onCreditVoidFailed(tender, voidPaymentResponse) } } catch (throwable: Throwable) { onVoidFailed() } }
  • 78.
    Coroutines went stablein 1.3 Similar things exist in other languages • E.g. async await has been in C# since 2014, F# since 2012 • JavaScript, Python have very similar syntax to C# • Golang has goroutines & channels • They are all very useful at simplifying your code We now have a great solution for the JVM (and others) The Kotlin solution is built from primitives that open up flexibility Summary
  • 79.
  • 80.

Editor's Notes

  • #21 Easy to understand each section Simple Sequential Code
  • #23 Simply code Sequential
  • #24 Simply code Sequential
  • #37 You get to use all the stand
  • #43 If the pools are created then dispatch time is faster