I have been trying to apply coroutine to my android app but found some strange thing about async coroutine. Base on this article
val deferred = async { … }
deferred.cancel()
val result = deferred.await() // throws JobCancellationException!
If you cancel the deferred before await code is called, it will throw exception. It seems like it just doesn't allow you to cancel an async coroutine. How do I cancel the deferred without throwing an exception?
Or the only way is just to add try-catch around every await? But that seems verbose to me. Is there any cleaner approach to it?
Calling await() after cancel() leads to CancellationException. From the docs of await() method:
This suspending function is cancellable. If the Job of the current
coroutine is cancelled or completed while this suspending function is
waiting, this function immediately resumes with CancellationException.
CancellationException is thrown "silently" without crashing an app, we can read that from the docs:
If exception is CancellationException then it is ignored (because that
is the supposed mechanism to cancel the running coroutine)
If you want somehow handle the exception, cleanup resources or want your code continue execution after calling await() use try-catch block as usual:
val deferred = async { ... }
deferred.cancel()
try {
val result = deferred.await()
} catch (e: CancellationException) {
// handle CancellationException if need
} finally {
// make some cleanup if need
}
// ... other code which will be executed if `await()` throws `CancellationException`
If you want to reuse a CoroutineScope after one of its jobs is cancelled, use a SupervisorJob for it. The SupervisorJob is not cancelled when its childs get cancelled.
val reuseableScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
Related
I have this piece of code:
// this method is used to evaluate the input string, and it returns evaluation result in string format
fun process(input: String): String {
val timeoutMillis = 5000L
val page = browser.newPage()
try {
val result = runBlocking {
withTimeout(timeoutMillis) {
val result = page.evaluate(input).toString()
return#withTimeout result
}
}
return result
} catch (playwrightException: PlaywrightException) {
return "Could not parse template! '${playwrightException.localizedMessage}'"
} catch (timeoutException: TimeoutCancellationException) {
return "Could not parse template! (timeout)"
} finally {
page.close()
}
}
It should throw exception after 5 seconds if the method is taking too long to execute (example: input potentially contains infinite loop) but it doesent (becomes deadlock I assume) coz coroutines should be cooperative. But the method I am calling is from another library and I have no control over its computation (for sticking yield() or smth like it).
So the question is: is it even possible to timeout such coroutine? if yes, then how?
Should I use java thread insted and just kill it after some time?
But the method I am calling is from another library and I have no control over its computation (for sticking yield() or smth like it).
If that is the case, I see mainly 2 situations here:
the library is aware that this is a long-running operation and supports thread interrupts to cancel it. This is the case for Thread.sleep and some I/O operations.
the library function really does block the calling thread for the whole time of the operation, and wasn't designed to handle thread interrupts
Situation 1: the library function is interruptible
If you are lucky enough to be in situation 1, then simply wrap the library's call into a runInterruptible block, and the coroutines library will translate cancellation into thread interruptions:
fun main() {
runBlocking {
val elapsed = measureTimeMillis {
withTimeoutOrNull(100.milliseconds) {
runInterruptible {
interruptibleBlockingCall()
}
}
}
println("Done in ${elapsed}ms")
}
}
private fun interruptibleBlockingCall() {
Thread.sleep(3000)
}
Situation 2: the library function is NOT interruptible
In the more likely situation 2, you're kind of out of luck.
Should I use java thread insted and just kill it after some time?
There is no such thing as "killing a thread" in Java. See Why is Thread.stop deprecated?, or How do you kill a Thread in Java?.
In short, in that case you do not have a choice but to block some thread.
I do not know a solution to this problem that doesn't leak resources. Using an ExecutorService would not help if the task doesn't support thread interrupts - the threads will not die even with shutdownNow() (which uses interrupts).
Of course, the blocked thread doesn't have to be your thread. You can technically launch a separate coroutine on another thread (using another dispatcher if yours is single-threaded), to wrap the libary function call, and then join() the job inside a withTimeout to avoid waiting for it forever. That is however probably bad, because you're basically deferring the problem to whichever scope you use to launch the uncancellable task (this is actually why we can't use a simple withContext here).
If you use GlobalScope or another long-running scope, you effectively leak the hanging coroutine (without knowing for how long).
If you use a more local parent scope, you defer the problem to that scope. This is for instance the case if you use the scope of an enclosing runBlocking (like in your example), which makes this solution pointless:
fun main() {
val elapsed = measureTimeMillis {
doStuff()
}
println("Completely done in ${elapsed}ms")
}
private fun doStuff() {
runBlocking {
val nonCancellableJob = launch(Dispatchers.IO) {
uncancellableBlockingCall()
}
val elapsed = measureTimeMillis {
withTimeoutOrNull(100.milliseconds) {
nonCancellableJob.join()
}
}
println("Done waiting in ${elapsed}ms")
} // /!\ runBlocking will still wait here for the uncancellable child coroutine
}
// Thread.sleep is in fact interruptible but let's assume it's not for the sake of the example
private fun uncancellableBlockingCall() {
Thread.sleep(3000)
}
Outputs something like:
Done waiting in 122ms
Completely done in 3055ms
So the bottom line is either live with this long thing potentially hanging, or ask the developers of that library to handle interruption or make the task cancellable.
I need to wrap some Java-callback function using timeout. Callback may be never called, so it should be interrupted with exception. Here was my first try:
fun main() = runBlocking {
withTimeout(500) {
async {
notCalledCallback()
}.await()
}
Unit
}
private suspend fun notCalledCallback() = suspendCoroutine<Boolean> { cont ->
startScanning(object : SomeCallback {
override fun done() {
cont.resume(true)
}
})
}
fun startScanning(callBack: SomeCallback) {
// callback may never be invoked
// callBack.done()
}
interface SomeCallback {
fun done()
}
I expected to have a TimeoutCancellationException after 500ms, but actually it never happens. However if I replace
async {
notCalledCallback()
}.await()
with
GlobalScope.async {
notCalledCallback()
}.await()
it starts to work. Why? What is the difference between async and GlobalScope.async in this case and why it works in latter case?
while (true) {
Thread.sleep(1)
}
This block of code does not comply with coroutine practices and doesn't offer the coroutine framework any opportunity to cancel it.
A correct implementation of infinityFunction() would be to simply call awaitCancellation. Alternately, you could replace Thread.sleep with delay.
Notably, using GlobalScope actually breaks the correct relationship between your coroutines (making the async block not a child of the calling coroutine), with the result that your main function doesn't wait for infinityFunction() to properly finish cancelling. While this appears to make your code work, it actually just conceals a worse bug.
The answer is actually very simple: suspendCoroutine() is not cancellable. You need to instead use a very similar function: suspendCancellableCoroutine().
Please be aware that ideally you should not only swap one function with another, but also properly cancel the asynchronous operation before resuming the coroutine. Otherwise you leak this background operation as it is entirely detached from your execution context. You can detect cancellations with cont.invokeOnCancellation(), as described in the documentation linked above.
If you use GlobalScope then you await() for the operation in your current execution context, but the operation itself runs in another context. In this case if you cancel, then you cancel waiting, but you don't cancel the operation and you don't care whether it completes or not.
I already have 2 long running task, each of them wrapped inside withContext. But now I want to run them parallel and wait for the result of them.
So I wonder if I use them like below(wrapped them with async):
val result1 = async {
withContext(Dispatcher.IO) {
someLongRunningBackgroundWork1...
....
}
}
val 2 = withContext(Dispatcher.IO) {
someLongRunningBackgroundWork2...
....
}
}
This works fine. But I wanted to understand if there is any trade offs behind the scene?
async(Dispatchers.IO) { /*...*/ } is identical logic as async { withContext(Dispatchers.IO) { /*...*/} }. In either case, uncaught exceptions will cause async to fail. If you use a bare async call like that inside a coroutine, it is a child coroutine and the exception will be propagated to the parent. If async is called on a specific CoroutineScope, it is not a child coroutine and will not propagate its exception unless await() or join() is called on it. Typically, when you want to run some parallel tasks in a coroutine and then continue, you wrap them together in a coroutineScope call.
You could do something like this in your coroutine to break down the parallel work. Any uncaught exception thrown inside coroutineScope is rethrown outside it, and will cancel any still-running children coroutines inside coroutineScope.
val (result1, result2) = coroutineScope {
val result1 = async(Dispatcher.IO) {
//someLongRunningBackgroundWork1...
//....
}
val result2 = withContext(Dispatcher.IO) {
//someLongRunningBackgroundWork2...
//....
}
result1.await() to result2
}
The advantage over what you have now is that if either of the code blocks fails, the other parallel tasks will automatically be cancelled.
As you say in the comment, your functions use withContext(IO) and you don't want to change that. Wrapping them in async will first start the coroutine in the current dispatcher (if you're on Android, that would be Main) and then immediately switch to IO. While it won't cost you a lot, it's still unnecessary. If you instead wrap the functions in async(IO), the coroutine will start directly on the correct dispatcher and the inner withContext(IO) { } will be basically a no-op.
As Tenfour04 brings up, launching off background work and awaiting on its result should be encompassed by a single unit of work, which should immediately fail as soon as any part of it fails. You don't show where you call await() on your launched coroutine, but as a rule both the async and await statements should occur within the same coroutineScope block. When that block is over, you have the guarantee that no ongoing work is left dangling in the background, and you also save time and resources by cancelling it if any other concurrent subtask (or the main task) fails.
As the title says, why do suspending functions throw exceptions in finally?
With regular functions, the finally-block executes all of them:
import kotlinx.coroutines.*
fun main() {
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
val job = GlobalScope.launch(handler) {
launch {
// the first child
try {
println("inside try")
delay(1000)
} finally {
println("Children are cancelled, but exception is not handled until all children terminate")
Thread.sleep(1000)
println("thread.sleep executed")
//foo()
println("The first child finished its non cancellable block")
}
}
launch {
// the second child
delay(10)
println("Second child throws an exception")
throw ArithmeticException()
}
}
Thread.sleep(1000000)
println("complete")
}
Here, for example, when I do Thread.sleep(1000) it prints:
"The first child finished its non cancellable block"
but if I change that line to delay(1000), it does not.
From my understanding, in a finally-block, the exception, if it exists, is thrown after executing the entire block.
But in this case, delay causes this exception to be thrown early.
On the other hand, Thread.sleep does not.
Can someone help explain?
Suspending functions in Kotlin work differently than blocking function.
When you cancel a Job, at the first suspension after the cancellation the execution will be stopped, even if you are in a finally block. If you use Thread.sleep(1000) instead of delay(1000) in your finally block, there are no suspensions taking place, because Thread.sleep() is blocking, not suspending, so your whole finally block gets executed.
Note that using blocking functions inside of suspending functions is an anti-pattern and should be avoided!!
To achieve this desired behavior without using blocking functions, use withContext(NonCancellable) {...} as described here.
Your example code should look like this:
fun main() {
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
val job = GlobalScope.launch(handler) {
launch {
// the first child
try {
println("inside try")
delay(1000000)
} finally {
withContext(NonCancellable) {
println("Children are cancelled, but exception is not handled until all children terminate")
delay(1000) // This suspension cannot be cancelled
println("delay executed")
//foo()
println("The first child finished its non cancellable block")
}
}
}
launch {
// the second child
delay(10)
println("Second child throws an exception")
throw ArithmeticException()
}
}
Thread.sleep(1000000)
println("complete")
}
The output:
inside try
Second child throws an exception
Children are cancelled, but exception is not handled until all children terminate
delay executed
The first child finished its non cancellable block
Caught java.lang.ArithmeticException
From my understanding, in a finally-block, the exception, if it exists, is thrown after executing the entire block.
This is not true. If a finally block throws an exception, it causes the finally block to terminate abruptly with that exception. Any exception that was thrown within try is thus discarded. This is exactly what happens in your case: the finally block of the first child coroutine receives a CancellationException on the delay(1000) line. Thread.sleep(1000) is a blocking, non-cancellable function, therefore it doesn't observe the cancellation.
You probably mixed up this with the fact that, if the try block throws an exception, then first the complete finally block is executed before throwing the exception. The finally block is required to complete normally in order for this to happen.
So I believe you aren't describing any difference in the behavior of plain and suspendable functions.
Can someone explain what exactly is the difference between these two?
When do you use one over the other?
Thanks in advance.
The best way to explain the difference is to explain the mechanism of coroutineScope. Consider this code:
suspend fun main() = println(compute())
suspend fun compute(): String = coroutineScope {
val color = async { delay(60_000); "purple" }
val height = async<Double> { delay(100); throw HttpException() }
"A %s box %.1f inches tall".format(color.await(), height.await())
}
compute() "fetches" two things from the network (imagine the delays are actually network operations) and combines them into a string description. In this case the first fetch is taking a long time, but succeeds in the end; the second one fails almost right away, after 100 milliseconds.
What behavior would you like for the above code?
Would you like to color.await() for a minute, only to realize that the other network call has long failed?
Or perhaps you'd like the compute() function to realize after 100 ms that one of its network calls has failed and immediately fail itself?
With supervisorScope you're getting 1., with coroutineScope you're getting 2.
The behavior of 2. means that, even though async doesn't itself throw the exception (it just completes the Deferred you got from it), the failure immediately cancels its coroutine, which cancels the parent, which then cancels all the other children.
This behavior can be weird when you're unaware of it. If you go and catch the exception from await(), you'll think you've recovered from it, but you haven't. The entire coroutine scope is still being cancelled. In some cases there's a legitimate reason you don't want it: that's when you'll use supervisorScope.
A Few More Points
Let's make two changes to our program: use supervisorScope as discussed, but also swap the order of awaiting on child coroutines:
suspend fun main() = println(compute())
suspend fun compute(): String = supervisorScope {
val color = async { delay(60_000); "purple" }
val height = async<Double> { delay(100); throw HttpException() }
"The box is %.1f inches tall and it's %s".format(height.await(), color.await())
}
Now we first await on the short-lived, failing height coroutine. When run, this program produces an exception after 100 ms and doesn't seem to await on color at all, even though we are using supervisorScope. This seems to contradict the contract of supervisorScope.
What is actually happening is that height.await() throws the exception as well, an event distinct from the underlying coroutine throwing it.
Since we aren't handling the exception, it escapes from the top-level block of supervisorScope and makes it complete abruptly. This condition — distinct from a child coroutine completing abruptly — makes supervisorScope cancel all its child coroutines, but it still awaits on all of them to complete.
So let's add exception handling around the awaits:
suspend fun compute(): String = supervisorScope {
val color = async { delay(60_000); "purple" }
val height = async<Double> { delay(100); throw Exception() }
try {
"The box is %.1f inches tall and it's %s".format(height.await(), color.await())
} catch (e: Exception) {
"there was an error"
}
}
Now the program does nothing for 60 seconds, awaiting the completion of color, just as described.
Or, in another variation, let's remove exception handling around awaits, but make the color coroutine handle the CancellationException, wait for 2 seconds, and then complete:
suspend fun compute(): String = coroutineScope {
val color = async {
try {
delay(60_000); "purple"
} catch (e: CancellationException) {
withContext(NonCancellable) { delay(2_000) }
println("color got cancelled")
"got error"
}
}
val height = async<Double> { delay(100); throw Exception() }
"The box is %.1f inches tall and it's %s".format(height.await(), color.await())
}
This does nothing for 2.1 seconds, then prints "color got cancelled", and then completes with a top-level exception — proving that the child coroutines are indeed awaited on even when the top-level block crashes.
I think Roman Elizarov explain it quite in details, but to make it short:
Coroutines create the following kind of hierarchy:
Parent Coroutine
Child coroutine 1
Child coroutine 2
...
Child coroutine N
Assume that "Coroutine i" fails. What do you want to happen with its parent?
If you want for its parent to also fail, use coroutineScope. That's what structured concurrency is all about.
But if you don't want it to fail, for example child was some kind of background task which can be started again, then use supervisorScope.
coroutineScope -> The scope and all its children fail whenever any of the children fails
supervisorScope -> The scope and all its children do NOT fail whenever any of the children fails
As #N1hk mentioned, if you use async the order of calling await matters. And if you're depending on a result from both asynch..await blocks, then it makes sense to cancel as early as possible so that's not an ideal example for supervisorScope
The difference is more apparent when using launch and join:
fun main() = runBlocking {
supervisorScope {
val task1 = launch {
println("Task 1 started")
delay(100)
if (true) throw Exception("Oops!")
println("Task 1 completed!")
}
val task2 = launch {
println("Task 2 started")
delay(1000)
println("Task 2 completed!")
}
listOf(task1, task2).joinAll()
println("Finished waiting for both tasks")
}
print("Done!")
}
With supervisorScope, the output would be:
Task 1 started
Task 2 started
Exception in thread "main" java.lang.Exception: Oops!
...
Task 2 completed!
Finished waiting for both tasks
Done!
With coroutineScope the output would be just:
Task 1 started
Task 2 started
CoroutineScope -> Cancel whenever any of its children fail.
SupervisorScope -> If we want to continue with the other tasks even when one fails, we go with the supervisorScope. A supervisorScope won’t cancel other children when one of them fails.
Here is a useful link for understanding of coroutine in details:
https://blog.mindorks.com/mastering-kotlin-coroutines-in-android-step-by-step-guide