Timo Drick
03/08/2021, 4:54 PMval job1 = launch {
try {
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
SystemClock.sleep(1000)
throw IOException("Wurst")
}
} catch (err: Throwable) {
Log.d("Test", "code executed")
}
}
launch {
delay(500)
job1.cancel()
}
In this code the Log.d line will be executed. Which is bad when e.g.: i want to show a error message after a network call failed but the fragment coroutine scope is already cancelled.
Is there any ellegant solution to avoid executing the catch when corotuine scope is already cancelled?Tijl
03/08/2021, 5:05 PMensureActive()
before you throw, but if you would use delay()
instead of sleep that would also just work.
however your catch is catching the CancellationException
so your message will always display, you need to narrow your catch or check it’s not CancellationException
ephemient
03/08/2021, 5:15 PMTimo Drick
03/08/2021, 9:17 PMChandler Cheng
03/08/2021, 9:18 PMephemient
03/08/2021, 9:37 PMTimo Drick
03/08/2021, 9:40 PMTijl
03/08/2021, 9:49 PMCancellationException
separately.Timo Drick
03/08/2021, 9:50 PMTijl
03/08/2021, 9:54 PMensureActive()
throwing exception or not throwing exceptions won’t magically solve whatever control flow problem you have.Timo Drick
03/08/2021, 9:56 PMval job1 = launch {
//try {
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
SystemClock.sleep(1000)
//throw IOException("Wurst")
}
//} catch (err: Throwable) {
Log.d("Test", "this code is not executed")
//}
}
launch {
delay(500)
job1.cancel()
}
This code shows that the happy path do work. So when i do a long running background operation and the coroutine is already cancelled nothing is executed any more. But in a try catch exception case it is not checked.val job1 = launch {
try {
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
SystemClock.sleep(1000)
throw IOException("Wurst")
}
} catch (err: Throwable) {
delay(1)
Log.d("Test", "this code is not executed")
}
}
launch {
delay(500)
job1.cancel()
}
Tijl
03/08/2021, 10:26 PMCancellationException
) if you canceled it, naturally.
Indeed throwing exceptions is not a suspension point (I guess this is what you mean by “interruptable”)
I think you’re seeing coroutines as a kind of threads that stop executing in the middle of what they’re doing, but that would be terrible.
Instead they’re cooperative, their behaviour is predictable, you know your code will run as normal until you come to a defined point (a method marked with suspend
)
Just think about it.. you have a piece of code that throws the exception, but sometimes you want the execution will stop right before that statement (intrupted as you say), and sometimes not (e.g. the cancel is called a little later). You will have to account and test for both states (with and without exception thrown), though the code that would have caused the condition you wanted to raise the exception for in both cases.Timo Drick
03/08/2021, 10:30 PMTijl
03/08/2021, 10:34 PMensureActive()
as the first statement in your catchsuspend
method} catch (err: Throwable) {
handleError(err)
}
// ...
suspend fun handleError(err:Throwable) {}
Timo Drick
03/08/2021, 10:36 PMTijl
03/08/2021, 10:38 PMTimo Drick
03/08/2021, 10:40 PMlaunch(Dispatchers.Main) {
showLoadingSpinner()
try {
val data = downloadData()
showData(data)
} catch (err: Throwable) {
showError()
} finally {
hideLoadingSpinner()
}
}
ephemient
03/08/2021, 10:41 PMcatch (err: Throwable)
like that is a bad idea, coroutines or not.Timo Drick
03/08/2021, 10:42 PMTijl
03/08/2021, 10:43 PMshowError
is marked suspend
you’re already done.Timo Drick
03/08/2021, 10:45 PMephemient
03/08/2021, 10:48 PMTimo Drick
03/08/2021, 10:48 PMlaunch(Dispatchers.Main) {
showLoadingSpinner()
val data = downloadData()
hideLoadingSpinner()
when(data) {
is Data -> showData(data)
is Error -> showError(data)
}
}
Tijl
03/08/2021, 10:49 PMTimo Drick
03/08/2021, 10:50 PMephemient
03/08/2021, 10:51 PMTimo Drick
03/08/2021, 10:51 PMTijl
03/08/2021, 10:58 PMTimo Drick
03/08/2021, 11:01 PMTijl
03/09/2021, 7:25 AMsuspend
. It doesn’t alter any other language mechanics (like throw), it’s essentially just a shortcut for throwing some blocks around your code, capturing some variables, and passing it to a dispatcher.
it’s something you can (and probably have) at some point basically implemented yourself in Java or some other language.Timo Drick
03/09/2021, 8:37 AMvb.button.setOnClickListener {
val job1 = launch {
try {
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
SystemClock.sleep(1000)
delay(10)
throw IOException("Wurst")
}
} catch (err: Throwable) {
Log.d("Test", "code executed")
}
}
launch {
delay(100)
job1.cancelChildren()
job1.cancel()
}
}
Tijl
03/09/2021, 8:40 AMsuspend
ensureActive()
will give the exact same result for cancellation, a CancellationException
will be thrown. Because, again, not much “magic” about coroutines, they don’t stop execution of a codepath somehow (which would require changing all kinds of language rules and implementation), they just throw this exception.} catch (err: Throwable) {
Log.d("Test", "code executed")
}
to
} catch(e: CancellationException) { throw e }
catch (err: Throwable) {
Log.d("Test", "code executed")
}
Timo Drick
03/09/2021, 8:51 AMwithContext(<http://Dispatchers.IO|Dispatchers.IO>) {
SystemClock.sleep(1000)
if (isActive) {
throw IOException("Wurst")
}
}
Tijl
03/09/2021, 8:53 AMTimo Drick
03/09/2021, 8:58 AMTijl
03/09/2021, 9:01 AMTimo Drick
03/09/2021, 9:08 AMTijl
03/09/2021, 9:12 AMTimo Drick
03/09/2021, 9:13 AMTijl
03/09/2021, 9:16 AM