在协程上下文中重新抛出异常

huangapple go评论51阅读模式
英文:

Rethrow Exception in a Coroutine Context

问题

在协程环境中,将LowLevelException转换为HighLevelException的最佳方法是使用supervisorScopeCoroutineExceptionHandler,就像您在提供的代码示例中所做的那样。这是一种有效的方法,可以捕获并处理异常。没有明显的更好方法来完成这个任务,所以您已经采取了一个合理的方法。

英文:

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
            }
        }
    }

    //...

huangapple
  • 本文由 发表于 2023年3月3日 22:12:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/75628160.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定