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.
Related
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.
I have a kotlin function just as following, And I expected it can wrap a sync IO action into async.
suspend fun <T> runIOAsync(f:suspend () -> T): Deferred<T> = coroutineScope{
async(Dispatchers.IO) {
f()
}
}
Then I have my code in the calling side like
runBlocking {
repeat(5) {
runIOAsync {
println(it)
println(Thread.currentThread())
Thread.sleep(3000)
println("After sleep $it")
}.await()
}
}
But the actual out put is
0
Thread[DefaultDispatcher-worker-1 #coroutine#2,5,main]
After sleep 0
1
Thread[DefaultDispatcher-worker-1 #coroutine#3,5,main]
After sleep 1
2
Thread[DefaultDispatcher-worker-1 #coroutine#4,5,main]
After sleep 2
3
Thread[DefaultDispatcher-worker-1 #coroutine#5,5,main]
After sleep 3
4
Thread[DefaultDispatcher-worker-1 #coroutine#6,5,main]
After sleep 4
Which seems all tasks from my function are executed serially. Any one can please help to give an explanation
Let's put aside runIOAsync for the moment.
You're using await() right after calling async(), which means that you're effectively waiting for the end of your async execution before executing the next one.
Instead, start all tasks first and then await all of them. For instance you can use awaitAll:
runBlocking {
List(5) {
async(Dispatchers.IO) {
println(it)
println(Thread.currentThread())
Thread.sleep(3000)
println("After sleep $it")
}
}.awaitAll()
}
Also, the way you're encapsulating the scope in runIOAsync is wrong, you will be waiting for the end of the async execution even without calling await() (coroutineScope is a suspending function that waits for all its child coroutines before resuming).
Instead, use coroutineScope to define the boundary of your coroutines executions, and you don't even have to await them. Since you don't need to get values from this code, you can also use launch instead of async here:
coroutineScope {
repeat(5) {
launch(Dispatchers.IO) {
println(it)
println(Thread.currentThread())
Thread.sleep(3000)
println("After sleep $it")
}
}
}
Declaring a suspend function returning a Deferred should be a red flag, and is quite confusing from an API standpoint: if you suspend it means you wait, if you return Deferred it means you don't wait (you immediately return a handle to some running computation). A function that does both would be quite weird.
If what you want is to make suspending code from IO-bound code, you can use instead the existing withContext function:
// this suspends until the code inside is done
withContext(Dispatchers.IO) {
// run some blocking IO code
}
However, note that this is independent from defining concurrent code. If you want to run multiple things concurrently, even with suspend functions, you'll need coroutine builders such as async or launch like in the above code.
I want to make a suspend function cancellable, but isActive isn't accessible. Is this just handled automatically?
suspend fun coolFunction() {
while (isActive) {
/* Do cool stuff */
}
}
To cooperate with cancellation, you can periodically suspend, most simply done by calling yield()
suspend fun coolFunction() {
while (true) {
yield()
/* Do cool stuff */
}
}
You can also support cancellation by checking CoroutineScope.isActive. But a suspend function on its own doesn't have direct access to the CoroutineScope it was called from. You would have to use something like coroutineContext[Job]!!.isActive, which is clumsy. isActive is more useful when you're directly composing a coroutine with something like launch rather than a suspend function that could be called from any scope.
You can cancel the job or coroutineScope that suspending function is running in so the suspending function will cancel.
private suspend fun CoroutineScope.cancelComputation() {
println("cancelComputation()")
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
// WARNING 🔥 isActive is an extension property that is available inside
// the code of coroutine via CoroutineScope object.
while (isActive) { // cancellable computation loop
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.")
/*
Prints:
cancelComputation()
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
*/
}
I need to start many coroutines in a for loop and get a callback in the Main thread after all the tasks are completed.
What is the best way to do this?
//Main thread
fun foo(){
messageRepo.getMessages().forEach {message->
GlobalScope.launch {
doHardWork(message)
}
}
// here I need some callback to the Main thread that all work is done.
}
And there is no variant to iterate messages in CoroutineScope. The iteration must be done in the Main thread.
You can wait until all tasks will be completed with awaitAll and then execute your callback in the main thread with withContext
fun foo() {
viewModelScope.launch {
messageRepo.getMessages().map { message ->
viewModelScope.async(Dispatchers.IO) {
doHardWork(message)
}
}.awaitAll()
withContext(Dispatchers.Main) {
// here I need some callback that all work is done.
}
}
}
Given we have a suspending function but this is not a CoroutineScope, how can we launch other coroutines such that those are associated with the current scope of whatever runs this suspending function?
Every suspendable function has access to the global variable coroutineContext which you can trivially wrap in CoroutineScope, but that is not its intended purpose. It's there so you can check at any point whether your coroutine was cancelled, reach the debug info like the job name, etc.
In the words of Roman Elizarov in his recent Medium post:
suspend fun doNotDoThis() {
CoroutineScope(coroutineContext).launch {
println("I'm confused")
}
}
Do not do this!
A suspendable function should not fire off concurrent work that may go on after it returns. It should only use concurrency to achieve parallel decomposition of a task, and that means it will wait for all the child coroutines to complete.
You should decide to either use a plain function that is the receiver of CoroutineScope (signaling the intention to launch concurrent work) or use a suspendable function that awaits on the completion of all the work it initiated.
So, if you want parallel decomposition, then use a coroutineScope or, possibly a supervisorScope block:
coroutineScope {
launch {
// ... task to run in the background
}
// ... more work while the launched task runs in parallel
}
// All work done by the time we reach this line
coroutineScope is a suspendable function and it won't complete until all the coroutines it launched complete.
You can create an extension function on CoroutineScope or function with CoroutineScope as a parameter:
fun CoroutineScope.doThis() {
launch { ... }
}
fun doThatIn(scope: CoroutineScope) {
scope.launch { ... }
}
Also you can use coroutineScope or supervisorScope, depending on your needs:
suspend fun someFun() = coroutineScope {
launch { ... }
}
suspend fun someFun() = supervisorScope {
launch { ... }
}
You could just use withContext() or coroutineScope() for launching another coroutine:
withContext(coroutineContext) {
launch { ... }
}
While the second would override the Job of the context, but reuse the context:
coroutineScope {
launch { ... }
}