+-
Kotlin:如何绕过CancellationException

我正在将一些旧的RxJava代码移植到协程。使用RxJava,我可以在自己的活动中执行此操作:

someBgOperation()
.as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(MyActivity.this)))
.subscribe(
    MyActivity.this::onSuccess,
    MyActivity.this::onError
);

如果活动被关闭,自动处置库将取消Observable。在这种情况下,RxJava不会调用错误处理程序,因此可以安全地在错误处理程序中执行与UI相关的操作,例如显示对话框。

现在,在Kotlin中,我们可以从Activity中的lifecycleScope启动此等效代码,如果使用ViewModel,则可以在viewModelScope中启动此等效代码:

viewModelScope.launch {
    try {
        someBgOperation()
    } catch (e: Exception){
        //show dialog
    }
}

活动关闭时,两个作用域都会自动取消,这与Autodispose一样。但是catch块不仅会执行someBgOperation本身抛出的正常错误,还会执行协程程序库在后台处理的CancellationException来执行取消操作。如果在活动关闭时尝试在其中显示对话框,则可能会出现新的异常。所以我被迫做这样的事情:

viewModelScope.launch {
    try {
        someBgOperation()
    } catch (ce: CancellationException){
        //do nothing, activity is closing
    } catch (e: Exception){
        //show dialog
    }
}

这比Rx版本更冗长,并且具有空的catch子句,该子句将在lint输出中显示警告。在其他情况下,在尝试捕获之后执行更多操作时,我被迫从CancellationException捕获返回以保持UI安全(这些返回被标记为返回)。我发现自己一次又一次地重复这个丑陋的模板。

是否有更好的方法来忽略CancellationException?

1
投票

我会考虑使用这种更简洁的语法:

viewModelScope.launch {
    try {
        someBgOperation()
    } catch (e: Exception){
        if (isActive) {
            //show dialog
        }
    }
}
0
投票

将其作为一个单独的答案发布,因为它完全不同,甚至可能有些杜鹃

您可以创建一个函数和助手类来吞咽CancellationExceptions,同时保持类似的语法:

viewModelScope.launch {
    tryCancellable {
        someBgOperation()
    } catchNotCancelled { e ->
        //show dialog
    }
}
/** Run the block of code and catch any throwable besides CancellationException into 
    the returned [AttemptResult]. */
suspend fun tryCancellable(block: suspend () -> Unit ): AttemptResult {
    var throwable: Throwable? = null
    try {
        block()
    } catch (e: CancellationException) {
    } catch (e: Throwable) {
        throwable = e
    }
    return AttemptResult(throwable)
}

class AttemptResult(val throwable: Throwable?) {
    /** Handle the wrapped throwable if it exists. */
    infix fun catchNotCancelled(block: (Throwable) -> Unit) {
        if (throwable != null)
            block(throwable)
    }

    fun rethrow() = throwable?.let { throw it }
}