kotlinx.coroutines: App exits after catching exception thrown by coroutine
I’m new to Kotlin and coroutines so maybe I’m doing something wrong, but…
The test code below throws an exception which is propagated to the caller in await().
The caller catches the exception, logs it, and ignores. So far so good.
But - immediately after this, the app exits like this, looks like the exception gets rethrown.
This is quite unexpected to me (but again I’m a novice). Do exceptions propagated by await() always get rethrown? What if I want to ignore it (show message to user, log, etc. but don’t want the exception to keep bubbling up)?
GlobalScope.launch(Dispatchers.Main) {
val run = async(Dispatchers.IO) {
if (System.currentTimeMillis() != 0L) {
throw IOException("Test")
}
"foobar"
}
try {
val res = run.await()
Log.i(TAG, "Result: " + res)
} catch (x: Throwable) {
Log.w(TAG, "Error:", x)
}
}
W MainActivity: Error:
W MainActivity: java.io.IOException: Test
W MainActivity: at org.kman.updatechecker.MainActivity$startCheckJob$2$run$1.invokeSuspend(MainActivity.kt:99)
W MainActivity: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
W MainActivity: at kotlinx.coroutines.DispatchedTask$DefaultImpls.run(Dispatched.kt:221)
W MainActivity: at kotlinx.coroutines.DispatchedContinuation.run(Dispatched.kt:67)
W MainActivity: at kotlinx.coroutines.scheduling.Task.run(Tasks.kt:94)
W MainActivity: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
W MainActivity: at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
W MainActivity: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:732)
^^^^^ This is from test code's Log.w(TAG, "Error:", x)
E AndroidRuntime: FATAL EXCEPTION: main
E AndroidRuntime: Process: org.kman.updatechecker, PID: 8026
E AndroidRuntime: java.io.IOException: Test
E AndroidRuntime: at org.kman.updatechecker.MainActivity$startCheckJob$2$run$1.invokeSuspend(MainActivity.kt:99)
E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
E AndroidRuntime: at kotlinx.coroutines.DispatchedTask$DefaultImpls.run(Dispatched.kt:221)
E AndroidRuntime: at kotlinx.coroutines.DispatchedContinuation.run(Dispatched.kt:67)
E AndroidRuntime: at kotlinx.coroutines.scheduling.Task.run(Tasks.kt:94)
E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:732)
^^^^^ But the exception continues to bubble up
W ActivityManager: Force finishing activity org.kman.updatechecker/.MainActivity
^^^^^ The app is terminated
I’m using Android Studio 3.2, Kotlin 1.3.0-rc-190-Studio3.2-1 and kotlinx-coroutines-android 1.0.0-RC1
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Comments: 23 (12 by maintainers)
@jcornaz I just tried it, yes nice that there is less “syntax noise” but (still!)…
The “recipe” you gave is very close to one found here:
https://proandroiddev.com/async-code-using-kotlin-coroutines-233d201099ff
Let me step back and restate what I’m trying to do.
Consider this all synchronous code, no coroutines, async / await’s or anything:
Things to note:
I am able to handle errors close to where they can occur, to show the most relevant message to the user (not just “error” but “error getting account / order info”);
Cancellations (without trying to continue with updating the UI which perhaps has gone away);
The operations are sequential (can’t get order object without account object), but I don’t want to make a new function that combines both and returns a single result - this is the function I’m writing here already.
The problem here is of course that getAccount / getOrder need to execute on an IO thread, and updating the UI needs to execute on the “main” (UI, GUI toolkit) thread.
Kotlin coroutines to the rescue! I can just run some parts of my code on an IO thread and other parts on the UI thread - but write the logic as if it were sequential and synchronous.
This magically executes every piece of code on the appropriate thread. Wonderful.
But for some reason, any exceptions thrown by getAccount / getOrder are now not caught in the catch blocks around the respective await()'s.
This is “astonitishing” again, there clearly are catch blocks around the awaits, and it’s awaits which propagate exceptions, right?
Exceptions are caught only in the top-level catch (“Log / show the exception”) now and are thrown out of async{} blocks. Before adding withContext, they were getting thrown out of await()'s.
And so, new questions if you don’t mind:
Why are my exceptions now getting thrown out of async{} (vs. before withConext: out of await)?
I feel like I’m stumbling in the dark, chanting magical incantations (which sometimes work, but more often turn random objects into stones or worse)…
Are there any guides that explain in more depth what’s going on?
I have read this:
https://kotlinlang.org/docs/reference/coroutines/exception-handling.html
… but it just says that “async exposes exceptions to uses” and doesn’t explain why the introduction of withContext exposes them out of async{} and not from await(),
PS:
I see in the exception guide
https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/exception-handling.md#exception-handling
that launch “propagates exceptions automatically” but async “exposes them to users”.
Indeed, changing the code snippet to use async instead of launch makes it work as I expect:
But but but… this still seems pretty weird to me (remember I’m a newbie at Kotlin):
Yes I know it’s possible to .await() on async and its type will be just Unit which is “void” but still strange when a particular coroutine “by its nature” doesn’t return / produce anything at all (and it can be argued that “returns nothing” isn’t quite the same as “returns void”).
Since Kotlin doesn’t have checked exceptions (yes I currently use Java), when writing a coroutine we won’t be “told” by the compiler that “hey this can result in an exception, be careful”
There is no exception here. Yes one did occur, but it was caught and not re-thrown.
That’s there in the code, look, the exception does not escape, it’s done for, dead-ended:
I’m sure there are reasons why things are as they are –
– would it be possible to explain the rationale behind the “automatic rethrow even if caught and ignored” that’s done by launch? I don’t see anything in the official docs.
Yes it is, just one thing, you switched the threads around.
As a side benefit you got better defined cancellation points (i.e. every time the code tries to reach into the UI state).
I see it now - yesterday I misread your
ViewControllerexample, and saw something (async) which wasn’t there.I now changed my code to use only
withContextwithoutasync:It now works much more to my taste.
Not “astounding” anymore. Exceptions get propagated and caught in the nearest
catchblocks.And then the whole
withContextblock now returns the “real” value type (not aDeferredwhich then needs to beawait’ed on!) which also simplifies code structure.Two remaining problems are that exceptions from inside
withContexttake priority over cancellations - and that it’s necessary to catchCancellationExceptionseparately from otherThrowable’s (to differentiate cancellations from errors).But for those I bet a utility function would work just fine.
You still use
asyncwhich is unnessacary in your code, and introduce the astonishing behavior.If I translate your first (blocking) version of
showOrderin an (non-blocking) equivalent with Kotlin coroutines it will be:You see that it is very much the same. The only difference are the
suspendkeyword for the function and the use ofwithContextwherever needed. This make the code as easy to read as if it was blocking.Of course you need to adapt the usage, since you can only call a suspending function from a coroutine:
Hi @a-kari, Both
asyncandlaunchare designed for parallel decomposition, butasyncreturns a result, whereas inlaunchthe result is irrelevant.However this isn’t a library issue, for this kind or discussion I suggest you to prefer different resources, like the official Kotlin forum.
I’m new to Kotlin coroutines, too. But as far as I understand,
asynccoroutine builder is designed for parallel decomposition of some large task.E.g. we have to combine results of two suspend functions:
If we just call the suspend functions to combine their results, they will be executed sequentially, so we will have to wait 3 seconds (
computeValue1()execution time +computeValue2()execution time) to get a result:But if we wrap the suspend functions with
async, then they will be executed concurrently. So the result time will be 2 seconds - i.e execution time of the longest function (computeValue2()in this case):The next question is
asyncexception handling.As it mentioned in #552, the parent coroutine shouldn’t wait all child
asyncs to finish if one failed (waiting is makes no sense, because in this case the parent couldn’t combine children results anyway).So, as far as I understand, structured concurrency has been introduced to
asyncbecause of that (to immediately kill the parent if an exception occurs in a childasync).So I have created my solution to work with
asyncand handle its exceptions:JVM:Android:Conclusion:
asyncshould be used only for parallel decomposition. For just doing something in other thread we should use classiclaunch/suspendfeatures.this is not about
withContext. It is just the fact that async make the parent fail as soon as async is failed. That’s it. I aggree it can be astonishing when comming from other technologies. This problem is discussed in #763.My point is “you don’t need it”. One need
asynconly for parallel decomposition. For other uses-cases just use suspending function (without usingasyncat all) and you will get simple code with a simple mental model, since it is the same than the one you use to write blocking code.You probably also want to follow #763 which address the problem of
async’s astonishing behavior.An async to do something and return a result (or exception or have the whole thing cancel).
And I am expecting to find an an easy, language- and runtime- supported method of doing this “sequentially” in terms of syntax, but “asynchronously” in terms of “what really happens” - which fits what you wrote in #342
Thanks, I’ll try it. Like I said, it seems that most tutorials (that I’ve seen) used launch / async so I initially started exploring in that direction.
Thank you for the clarification and for your time.
@kmansoft please consider this code, I hope this helps you.