Kotlin coroutine execution order? - kotlin

Can someone enlighten me about what is going on in this code example:
import kotlinx.coroutines.*
import kotlin.time.*
fun subTask(scope: CoroutineScope): Job {
val job = scope.launch(Dispatchers.IO){
println("SubTask started.")
delay(500L)
println("SubTask ending.")
}
job.invokeOnCompletion(object: CompletionHandler{
override fun invoke(cause: Throwable?) {
println("SubTask completed.")
}
})
return job
}
fun main() {
val duration = measureTime {
runBlocking {
val job = withContext(Dispatchers.IO) {
subTask(this)
}
println("Waiting for SubTask")
job.join()
}
}
println("Duration: $duration")
}
(Click here for Kotlin playground)
I was expecting to see
SubTask started.
Waiting for SubTask
SubTask ending.
SubTask completed.
Duration: 600ms
But I am getting
SubTask started.
SubTask ending.
SubTask completed.
Waiting for SubTask
Duration: 600ms
Why isn't the subTask run in parallel with main()?

withContext suspends until it is complete. Since you are passing the scope of the withContext to subTask() and subTask() uses it to launch its job, withContext will suspend until this other launched job completes. If subTask() used a different CoroutineScope, or if you had not wrapped your subTask() call in withContext, you would have had your expected behavior.

Related

How to understand Coroutine Process with Example in kotlin

I'm studying coroutine with docs and some examples.
testing some examples, I found that in same CoroutineScope it works sequentially.
For example
Below code is working sequentially.
fun main() = runBlocking {
coroutineScope {
launch {
delay(1000)
println("Start")
}
}
coroutineScope {
launch {
delay(2000)
println("World2")
}
launch {
delay(1000)
println("World1")
}
println("Hello")
}
println("Done")
}
/*
Start
Hello
World1
World2
Done
*/
But If I change above codes like this
fun main() = runBlocking {
launch {
delay(1000)
println("Start")
}
coroutineScope {
launch {
delay(2000)
println("World2")
}
launch {
delay(1000)
println("World1")
}
println("Hello")
}
println("Done")
}
/*
Hello
Start
World1
World2
Done
*/
Removing first CoroutineScope, the result sequence is changed.
In My Opinion, I think this is a problem of job state, but not sure.
Is there anyone who can clarify this issue?
coroutineScope suspends until all its children coroutines have finished their execution. In your first case, the coroutineScope waits until the launch completes and prints Start, that's why you see Start as the first output on console.
In the second part, you removed the coroutineScope, so now it just launches a new coroutine and moves to the next step (launch doesn't suspend). Then in the coroutineScope you launch two other coroutines (using launch) and print Hello. That's why the Hello gets printed first. All other print statements are waiting for the delay to finish.
After 1 second, the first launch completes and prints Start followed by World1 and World2. Now when this coroutineScope finishes, the program control moves to the final print statement and prints Done.

delay() block its coroutine when using thread pool as the dispatcher?

Play with Kotlin coroutine, I am confused by the results of my toy code:
import kotlinx.coroutines.*
import java.util.concurrent.Executors
suspend fun task(sleepTime: Long) {
println("start task in Thread ${Thread.currentThread()}")
delay(sleepTime)
//yield()
println("end task with context in Thread ${Thread.currentThread()}")
}
println("start")
runBlocking {
val handler = CoroutineExceptionHandler() { context, ex ->
println("Caught: $context | ${ex.message}")
}
Executors.newFixedThreadPool(2)
.asCoroutineDispatcher().use { dispatcher ->
launch(dispatcher + handler) { task(100) }
launch(Dispatchers.IO + handler) { task(1000) }
println("called tasks ${Thread.currentThread()}")
}
}
println("done")
output:
start
start task in Thread Thread[pool-1-thread-1 #coroutine#2,5,main]
called tasks Thread[main #coroutine#1,5,main]
start task in Thread Thread[DefaultDispatcher-worker-2 #coroutine#3,5,main]
end task with context in Thread Thread[DefaultDispatcher-worker-2 #coroutine#3,5,main]
done
My question is why coroutine#1 does not continue? If I change delay() to yield(), it worked:
start
start task in Thread Thread[pool-1-thread-1 #coroutine#2,5,main]
end task with context in Thread Thread[pool-1-thread-2 #coroutine#2,5,main]
called tasks Thread[main #coroutine#1,5,main]
start task in Thread Thread[DefaultDispatcher-worker-1 #coroutine#3,5,main]
end task with context in Thread Thread[DefaultDispatcher-worker-1 #coroutine#3,5,main]
done
Closeable.use executes the code inside it and then closes the Closable, in this case your dispatcher. Since launched coroutines are run asynchronously, use doesn't wait for them to finish, and shuts down the dispatcher right away, thereby cancelling your coroutines. It's possible yield() happens so quickly that they have time to finish before getting cancelled.
If you move your use block outside the runBlocking block, then it waits for them to finish.
fun main() {
println("start")
Executors.newFixedThreadPool(2)
.asCoroutineDispatcher().use { dispatcher ->
runBlocking {
val handler = CoroutineExceptionHandler() { context, ex ->
println("Caught: $context | ${ex.message}")
}
launch(dispatcher + handler) { task(100) }
launch(Dispatchers.IO + handler) { task(1000) }
println("called tasks ${Thread.currentThread()}")
}
}
println("done")
}

NonCancellable coroutine gets cancelled

I'm trying to experiment with non-cancellable coroutines and I wrote the following code:
fun main(): Unit = runBlocking {
// launch first coroutine
coroutineScope {
val job1 = launch {
withContext(NonCancellable) {
val delay = Random.nextLong(from = 500, until = 5000)
println("Coroutine started. Waiting for ${delay}ms")
delay(delay)
println("Coroutine completed")
}
}
delay(300) // let it print the first line
println("Cancelling coroutine")
job1.cancelAndJoin()
}
}
Output:
Coroutine started. Waiting for 1313ms
Cancelling coroutine
Coroutine completed
So far, everything works as expected. However, if I pass the NonCancellable context (or rather, Job) directly in the launch function, the behaviour changes and the coroutine is cancelled:
fun main(): Unit = runBlocking {
// launch first coroutine
coroutineScope {
val job1 = launch(context = NonCancellable) {
val delay = Random.nextLong(from = 500, until = 5000)
println("Coroutine started. Waiting for ${delay}ms")
delay(delay)
println("Coroutine completed")
}
delay(300) // let it print the first line
println("Cancelling coroutine")
job1.cancelAndJoin()
}
}
Output:
Coroutine started. Waiting for 4996ms
Cancelling coroutine
Why is the second snippet producing a different output?
The job you pass as an argument to a launch method is not the job of the launched coroutine, but its parent job.
In the second snippet above, NonCancellable is the parent job of the job1. As the job1 is just a normal job, it's cancellable (but its parent isn't).

Does async block when using await

In the following code two asyncs are run:
import kotlinx.coroutines.*
import kotlin.system.*
fun main() = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
two.await()
one.await()
println("Finished")
}
}
suspend fun doSomethingUsefulOne(): Int {
delay(3000L)
println("first")
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L)
println("second")
return 29
}
Why is "Finished" not printed first? The docs says this about await:
Awaits for completion of this value without blocking a thread
The complete quote is:
Awaits for completion of this value without blocking a thread and
resumes when deferred computation is complete, returning the resulting
value or throwing the corresponding exception if the deferred was
cancelled.
Source
without blocking a thread:
The execution inside other async coroutine is not blocked. Track the time and you'll see that there was no 'pause' in time execution.
resumes when deferred computation is complete: the main thread will wait to continue on following code (println("Finished")), since you force it by calling await() to wait for the returned value. But the executions inside coroutines are not blocked.
EDIT
Considering the comments, I agree with AndroidDev on misleading documentation. It should be changed to:
Awaits for completion of this value without blocking other coroutines or
threads...
or
Awaits for completion of this value blocking only the current thread ...
By slightly modifying the code, one will gain some insights. Let's add the elapsed time and the current thread to the print statements to see, what's going on:
Execute on kotlin playground
import kotlinx.coroutines.*
import kotlin.system.*
private val started = System.currentTimeMillis()
private fun debug(s: String) {
val elapsed = System.currentTimeMillis() - started
println("[$elapsed] $s -- ${Thread.currentThread()}")
}
fun main() = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
debug("awaiting")
two.await()
one.await()
debug("Finished")
}
debug("time = $time")
}
suspend fun doSomethingUsefulOne(): Int {
delay(3000L)
debug("first")
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L)
debug("second")
return 29
}
When run with -Dkotlinx.coroutines.debug one should see something similar to
[46] awaiting -- Thread[main #coroutine#1,5,main]
[1056] second -- Thread[main #coroutine#3,5,main]
[3054] first -- Thread[main #coroutine#2,5,main]
[3054] Finished -- Thread[main #coroutine#1,5,main]
[3054] time = 3013 -- Thread[main #coroutine#1,5,main]
There is only one single thread.
If await had blocked the thread, the program could not have terminated.

why "repeat" function is cancelable in kotlin

fun main(args: Array<String>) = runBlocking<Unit> {
val job = launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
job.join() // waits for job's completion
println("main: Now I can quit.")
}
this code not check isActive or use suspend function but can canceled
Here's the analogy with Java threads:
1) Checking the interrupted flag explicitly:
while (!Thread.interrupted()) {
// loop code
}
2) Calling interruptible operations:
while (true) {
Thread.sleep(1);
// loop code
}
In both cases the thread will respond to a raised interrupt flag.
In coroutines, delay() is the counterpart of Thread.sleep() and the isActive flag is the counterpart of the Thread.interrupted flag.
Therefore, when you write
delay(1)
the coroutine will be scheduled off the thread and, when its time to resume comes, inside the continuation.resume() call it will first check the isActive flag. If it's raised, it will throw a CancellationException instead.
Because delay() is a suspend function.
Thread.sleep() is not a suspend function.
If you replace delay(500L) with Thread.sleep(500), then it will not be cancellable on time.