Is this setup somehow possible?
scopeA.launch {
try {
scopeB.launch {
throw SomeException()
}
} catch (e: SomeException) {
// Want to catch here
}
}
You seem to wish for contradictory things: both launch a concurrent coroutine and suspend the current work until it completes, catching possible exceptions. You must decide what you actually want:
If you want concurrency, then handle the exception within the child coroutine:
scopeA.launch {
launch(dispatcherB) {
try {
throw SomeException()
} catch (e: SomeException) {
// handle
}
}
}
If you don't want it, don't launch a coroutine but use withContext(dispatcherB) instead:
scopeA.launch {
try {
withContext(dispatcherB) {
throw SomeException()
}
} catch (e: SomeException) {
// handle
}
}
If you literally want two coroutines with decoupled lifecycles to communicate, you must introduce a decoupled communication mechanism as well. For example, you can set up a Channel<SomeException> or a Flow<SomeException>.
Related
Hi I'm playing around kotlin and I want to catch and throw our the exception, so my code is like this
runBlocking {
coroutineScope {
nonNullRecords.forEach {
launch(Dispatchers.IO) {
val time = measureTimeMillis {
try {
process(it)
} catch (e: Exception) {
throw Exception(e)
}
}
}
}
}
}
the process function is s suspend function.
So the thing is that in this case, if the process function has an exception(which is HttpTimeoutException), my service will crash which is excepted. But if I don't use the try catch, then my service will ignore the exception.
and further more, if I use throw e instead of throw Exception(e), it will also ignore the exception
May I know how this works? thanks so much
This is because Ktor's HttpRequestTimeoutException extends CancellationException, which is an exception used internally by coroutines to implement the cancellation mechanism. This is why this particular exception is not considered a real problem and is silently ignored.
This was fixed and will be released in Ktor 2.0.0:
https://youtrack.jetbrains.com/issue/KTOR-3192
Background: I am fetching data from Bluetooth and after every packet is received it is processed. What I am trying to do is to start timeout when data processing finishes and stop the timer when a new packet is received.
Tried creating a timeout logic using Flow. I created a short snippet to test if it works:
class ExceptionPropagationTest {
#Test
fun test()= runBlocking {
println(get(coroutineContext))
}
suspend fun get(coroutineContext: CoroutineContext) = withContext(coroutineContext) {
try {
enableDataTransferTimeout()
delay(3000)
"Result"
} catch (e: IllegalStateException) {
println("Exception caught ${System.currentTimeMillis()}")
"No Result"
}
}
private fun CoroutineScope.enableDataTransferTimeout() {
flowOf("1").onEach {
delay(500)
doSomething()
throw IllegalStateException()
}.launchIn(this)
}
private suspend fun doSomething(){
// Do some suspending work
}
}
Above code first prints:
Exception caught [CURRENT_TIME]
Then logs exceptions stack trace and crashes:
java.lang.IllegalStateException at
com.app.ExceptionPropagationTest$enableDataTransferTimeout$1.invokeSuspend(ExceptionPropagationTest.kt:49)
(Coroutine boundary) at
com.app.ExceptionPropagationTest$test$1.invokeSuspend(ExceptionPropagationTest.kt:32)
Caused by: java.lang.IllegalStateException
Question: Is there any way to catch the exception and return value without propagating the exception to parent scope?
If it is not possible with flow any other solution or suggestion is welcome.
You can use the catch method. Docs here
flowOf("1")
.map {
delay(500)
doSomething()
throw IllegalStateException()
}
.catch { ... } // catches exceptions in map or other operands you applied
.collect()
According to the source of Closable.use, if an error occurs, an exception will be thrown.
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
when {
apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
this == null -> {}
exception == null -> close()
else ->
try {
close()
} catch (closeException: Throwable) {
// cause.addSuppressed(closeException) // ignored here
}
}
}
In most examples of Closable.use, try-catch is not used as shown below.
Why isn't error handling needed? Is it safe?
BufferedReader(FileReader("test.file")).use { return it.readLine() }
This line
BufferedReader(FileReader("test.file")).use { return it.readLine() }
is not safe. Reading and closing the reader can both throw IOExceptions, which are not RuntimeExceptions (caused by programming errors). That means leaving them uncaught exposes your app to crashing from things outside your control.
Since Kotlin doesn't have checked exceptions, the compiler won't warn you about this. To do this safely, you need to wrap it in try/catch. And if you want to handle read errors differently than close errors, you either need to have inner and outer try/catch statements:
try {
BufferedReader(FileReader("test.file")).use {
try {
return it.readLine()
catch (e: IOException) {
println("Failed to read line")
}
}
} catch (e: IOException) {
println("Failed to close reader")
}
or wrap the whole thing and extract any suppressed exceptions, but then its cumbersome to distinguish between them:
try {
BufferedReader(FileReader("test.file")).use { return it.readLine() }
} catch (e: IOException) {
val throwables = listOf(e, *e.suppressed)
for (throwable in throwables)
println(throwable.message)
}
But in practice, you're probably not going to react differently to various IOExceptions, so you can just put the one try/catch outside.
We see from Kotlin documentation what is the purpose of the use function:
Executes the given block function on this resource and then closes it
down correctly whether an exception is thrown or not.
This function closes the resource properly if the block function completed successfully or threw an exception. It is your responsibility to handle the result of the block function.
If an exception was thrown and there is a way to handle it and proceed with code execution, use a try/catch. If there is nothing to do about it and control should be passed to the caller, it is not necessary to use a try/catch.
I am trying to do simple request to backend with coroutines
uiScope.launch {
try {
result = URL("https://httpbin.org/get").readText()
text.text = result
} catch (error: Error) {
text.text = error.message
} finally {
log(this#MainActivity,result)
}
}
but this exeption is thrown:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
How to solve it?
I found solution. I can not access UI component from another thread, at the same time I can not make internet request on the main thread. So I should chouse one of them. Solution was to use ViewModel components and update it's LiveDate value which subsequently will chane the UI
var viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
viewModel.selected.observe(this, Observer{ users ->
text.text = users
})
uiScope.launch {
try {
result = URL("http://jsonplaceholder.typicode.com/posts").readText()
viewModel.selected.postValue(result)
} catch (error: Error) {
viewModel.selected.postValue(error.toString())
}
}
log(this#MainActivity,result)
Your uiScope isn't set up correctly, apparently its dispatcher is not Dispatchers.Main. So the first thing to fix is your implementation of the coroutineContext property, which should be
override val coroutineContext = Dispatchers.Main + SupervisorJob()
Once you fix that, your code will be making a blocking call on the UI thread. To make the blocking call on a background thread, but still keep the rest of the coroutine on the UI thread, write
uiScope.launch {
try {
text.text = withContext(Dispatchers.IO) {
URL("https://httpbin.org/get").readText()
}
} catch (e: Exception) {
text.text = e.message
} finally {
log(this#MainActivity, result)
}
}
I've been having problems figuring out error handling with coroutines that I've narrowed down to this unit test with the following steps:
I create a coroutine scope, with any dispatcher.
I throw an exception anywhere within this scope in an async block (or even in a nested async block).
I call await on the returned deferred value and handle the exception.
This is all fine. However, when I try to use the same coroutine scope to launch a new coroutine, this always completes exceptionally with the same exception.
Here is the test:
fun `when you throw an exception in a coroutine scope, is the coroutine scope dead?`() {
val parentJob = Job()
val coroutineScope = CoroutineScope(parentJob + Dispatchers.Default)
val deferredResult = coroutineScope.async { throw IllegalStateException() }
runBlocking {
try {
deferredResult.await()
} catch (e: IllegalStateException) {
println("We caught the exception. Good.")
}
try {
coroutineScope.async { println("we can still use the scope") }.await()
} catch (e: IllegalStateException) {
println("Why is this same exception still being thrown?")
}
}
}
Here is the output of the test:
We caught the exception. Good.
Why is this same exception still being thrown?
Why is this happening?
My understanding was that you could handle exceptions normally and recover from them with coroutines.
How should I deal with exceptions?
Do I need to create a new coroutineScope?
Can I never throw exceptions if I want to keep using the same coroutineScope?
Should I return Either<Result, Exception>?
I've tried using CoroutineExceptionHandler but I still get the same results.
Note I'm using Kotlin 1.3
When you start a coroutine in a scope (using either async or launch), then a failure of a coroutine by default cancels this scope to promptly cancel all the other children. This design avoid dangling and lost exceptions.
The general advice here is:
Don't use async/await unless you really need concurrency. When you design your code with suspending functions there is no much need to use async and await.
If you do need concurrent execution, then follow the pattern:
coroutineScope {
val d1 = async { doOne() }
val d2 = async { doTwo() }
...
// retrieve and process results
process(d1.await(), d2.await(), .... )
}
If you need to handle a failure of a concurrent operation, then put try { ... } catch { ... } around coroutineScope { ... } to catch a failure in any of the concurrently executing operations.
There are additional advanced mechanisms (like SupervisorJob) that allow fine-grained exception handling. You can read more in the documentation https://kotlinlang.org/docs/reference/coroutines/exception-handling.html