英文:
Rethrow Exception in a Coroutine Context
问题
在协程环境中,将LowLevelException
转换为HighLevelException
的最佳方法是使用supervisorScope
和CoroutineExceptionHandler
,就像您在提供的代码示例中所做的那样。这是一种有效的方法,可以捕获并处理异常。没有明显的更好方法来完成这个任务,所以您已经采取了一个合理的方法。
英文:
I'm using intensively coroutine to create services, but I'm facing an issue about transforming Exception from inside my service to outside. Here is a synthesis of what I would like to do without Coroutine (here is the full example in a playground :
class MyService {
fun myBigComputation(type: MyServiceType) {
try {
for (i in (0..10_000)) {
mySubComputation(type, i)
}
} catch (e: LowLevelException) {
throw HighLevelException(type, e)
}
}
private fun mySubComputation(type: MyServiceType, i: Int) {
...
// something bad happend
throw LowLevelException(type)
}
...
}
You can see that I'm transforming the LowLevelException to HighLevelException. What is the best way to do that in a coroutine context.
That is not working as LowLevelException fails all the structure until the supervisorScope Playground
suspend fun main() = coroutineScope {
val service = MyService()
supervisorScope {
for (type in MyService.MyServiceType.values()) {
launch(CoroutineExceptionHandler { _, e ->
if (e is HighLevelException) {
println("ERROR: ${e.message}")
}
}) {
service.myBigComputation(type)
}
}
}
}
class MyService {
suspend fun myBigComputation(type: MyServiceType) = coroutineScope {
launch(CoroutineExceptionHandler { _, e ->
if (e is LowLevelException) {
throw HighLevelException(type, e)
}
}) {
for (i in (0..10)) {
mySubComputation(type, i)
}
}
}
private fun mySubComputation(type: MyServiceType, i: Int) {
if (i < 5 || type != MyServiceType.type1) {
print(".")
} else {
// something bad happend
println("something bad happened but exception kill all")
throw LowLevelException(type)
}
}
class LowLevelException(val type: MyServiceType): Exception()
enum class MyServiceType {
type1,
type2,
type3
}
}
class HighLevelException(val type: MyService.MyServiceType, e: Exception): Exception("Exception for type $type", e)
I did that, but I'm pretty sure that there is a better way, no ? Playground
suspend fun main() = coroutineScope {
val service = MyService()
supervisorScope {
for (type in MyService.MyServiceType.values()) {
launch(CoroutineExceptionHandler { _, e ->
if (e is HighLevelException) {
println("ERROR: ${e.message}")
}
}) {
service.myBigComputation(type)
}
}
}
}
class MyService {
suspend fun myBigComputation(type: MyServiceType) = supervisorScope {
launch(CoroutineExceptionHandler { _, e ->
if (e is LowLevelException) {
throw HighLevelException(type, e)
}
}) {
for (i in (0..10)) {
mySubComputation(type, i)
}
}
}
//...
}
答案1
得分: 1
你试图通过使用 CoroutineExceptionHandler 来使事情过于复杂化。CoroutineExceptionHandler 用于处理未捕获的异常。在它被调用时,你的协程已经抛出并失败了。它的设计意图是非常高级的行为,有点类似于 Java 中的 uncaughtExceptionHandler
,如果你熟悉的话。
但是,你还将 CoroutineExceptionHandler 附加到了子协程上。在子协程上设置 CoroutineExceptionHandler 没有任何效果,因为它仅用于处理未捕获的异常,而子协程会将异常传播给它们的父协程。请参阅文档中的第二段 链接 进行解释。
所以你的代码应该更像这样:
suspend fun main() {
val handler = CoroutineExceptionHandler { _, e ->
if (e is HighLevelException) {
println("ERROR: ${e.message}")
}
}
runBlocking(handler) {
val service = MyService()
supervisorScope {
for (type in MyService.MyServiceType.values()) {
launch {
service.myBigComputation(type)
}
}
}
}
}
class MyService {
suspend fun myBigComputation(type: MyServiceType) {
try {
for (i in (0..10)) {
mySubComputation(type, i)
}
} catch(e: Exception) {
throw if (e is LowLevelException) {
HighLevelException(type, e)
} else {
e
}
}
}
//...
}
英文:
You're over-complicating it by trying to use CoroutineExceptionHandler for this. CoroutineExceptionHandler is for uncaught exceptions. By the time it is called, your coroutine has already thrown and failed. It is intended to be a very high level behavior, kind of like uncaughtExceptionHandler
in Java, if you're familiar with that.
But also, you're attaching your CoroutineExceptionHandler to child coroutines. Setting a CoroutineExceptionHandler on a child coroutine has no effect, because it is only for handling uncaught exceptions, and child coroutines propagate exceptions to their parents. See the second paragraph in the documentation here for the explanation.
So your code should look more like:
suspend fun main() {
val handler = CoroutineExceptionHandler { _, e ->
if (e is HighLevelException) {
println("ERROR: ${e.message}")
}
}
runBlocking(handler) {
val service = MyService()
supervisorScope {
for (type in MyService.MyServiceType.values()) {
launch {
service.myBigComputation(type)
}
}
}
}
}
class MyService {
suspend fun myBigComputation(type: MyServiceType) {
try {
for (i in (0..10)) {
mySubComputation(type, i)
}
} catch(e: Exception) {
throw if (e is LowLevelException) {
HighLevelException(type, e)
} else {
e
}
}
}
//...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论