Kotlin Coroutines: Difference between revisions
Line 146: | Line 146: | ||
*Unconfined thread | *Unconfined thread | ||
*Specific thread | *Specific thread | ||
You specify the context in the coroutine builder | You specify the context in the coroutine builder. | ||
*Unconfined | *default (not specified context ForkJoinPool.commonPool-xxx) | ||
* | *Unconfined (default main thread) | ||
*CommonPoool | *DefaultDispatcher (default ForkJoinPool.commonPool-xxx) | ||
*newSingleThreadContext | *CommonPoool (default ForkJoinPool.commonPool-xxx) | ||
* | *newSingleThreadContext (default OwnThread) | ||
*coroutineContext (default main thread) |
Revision as of 23:02, 27 December 2020
Moores Law
I am doing this because of this graph
Previously there is fork/join for asynchronous but this code is far more complicated than it probably needs to be
override fun compute(): Long {
return if (high - low <= SEQUENTIAL_THRESHOLD) {
(low until high)
.map { array[it].toLong() }
.sum()
} else {
val mid = low + (high - low) / 2
val left = Sum(array, low, mid)
val right = Sum(array, mid, high)
left.fork()
val rightAns = right.compute()
val leftAns = left.join()
leftAns + rightAns
}
}
Using the suspend approach is far more easier to read
suspend fun compute(array: IntArray, low: Int, high: Int): Long {
// println("low: $low, high: $high on ${Thread.currentThread().name}")
return if (high - low <= SEQUENTIAL_THRESHOLD) {
(low until high)
.map { array[it].toLong() }
.sum()
} else {
val mid = low + (high - low) / 2
val left = async { compute(array, low, mid) }
val right = compute(array, mid, high)
return left.await() + right
}
}
Coroutines
Introduction
There are many co-routine builders (maybe)
- runBlocking (wait for co-routine to finish used for unit tests)
- launch non-blocking
- run
- async
Co-routines are lightweight threads and you can run many more co-routines than threads. They are scheduled onto a thread so they do not necessarily run on the same thread. A delay operation does not stop the thread only the co-routine. e.g.
...
launch {
delay(1000)
println("world")
}
It is very important not to use blocking code in a co-routine. As above delay is non-blocking to the thread but does delay the co-routine whereas Thread.sleep(5000) blocking.
Join ( job.join() )
Fairly simple
fun main(args: Array<String>) = runBlocking {
val job = launch {
delay(1000)
println("world")
}
job.join()
}
Cancel ( job.cancelAndJoin() )
For Cancel we do cancel and join or of course we use the cancelAndJoin(). This cancels because delay() checks for cancel.
fun main(args: Array<String>) = runBlocking {
val job = launch {
repeat(1000) {
delay(100)
println(".")
}
delay(100)
job.cancel()
job.join()
// Or
// job.cancelAndJoin()
}
Yield ( yield() ) or isActive
We can use yield within out own code or isActive() if we want to do stuff beyond yield.
fun main(args: Array<String>) = runBlocking {
val job = launch {
repeat(1000) {
yield()
// if (!isActive) throw CancellationException()
println(".")
}
delay(100)
job.cancelAndJoin()
}
Warning We must do a return to a non local return, i.e. a return outside of our loop. The code below will not work
fun main(args: Array<String>) = runBlocking {
val job = launch {
repeat(1000) {
yield()
if (!isActive) return@repeat
println(".")
}
job.cancelAndJoin()
}
Exceptions in Coroutines
We need to be careful as ever in cancelling and making sure we understand how the co-routine is torn down below demonstrate how this could be done. The run(NonCancellable) allow you to run a suspend function within your handling but be careful
...
try {
...
} catch(ex: CancellationException) {
println("Cancelled: ${ex.message}")
} finally {
run(NonCancellable) {
println("Forced non Cancel")
}
}
delay(100)
job.cancel(CancellationException("Because I can"))
job.join()
}
Summary for Exceptions
- Can be use to specify the reason why
- job.cancel(CancellationException("why"))
- Can specify any exception
- Job.cancel(SomeExceptionType()_
- Be Careful with this
- if using launch will tear down the thread/kill application
- Can use it with the async co-routine builder
Timeouts withTimeout() and withTimeoutWithNull()
These support timeout withTimeout() we have to wrap out co-routine with try catch. With withTimeoutWithNull() we only need to test the job for null to know if we completed.
Contexts
Introduction
Contexts provide a coroutine dispatcher to determine which thread the corountine is run on. Coroutines can run on
- Pool thread
- Unconfined thread
- Specific thread
You specify the context in the coroutine builder.
- default (not specified context ForkJoinPool.commonPool-xxx)
- Unconfined (default main thread)
- DefaultDispatcher (default ForkJoinPool.commonPool-xxx)
- CommonPoool (default ForkJoinPool.commonPool-xxx)
- newSingleThreadContext (default OwnThread)
- coroutineContext (default main thread)