Kotlin:如何绕过 CancellationException

Mis*_*ith 6 android cancellation kotlin kotlin-coroutines

我正在将一些旧的 RxJava 代码移植到 Coroutines。使用 RxJava,我可以在我的活动中做到这一点:

someBgOperation()
.as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(MyActivity.this)))
.subscribe(
    MyActivity.this::onSuccess,
    MyActivity.this::onError
);
Run Code Online (Sandbox Code Playgroud)

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

现在,在 Kotlin 中,我们可以lifecycleScope在 Activity 中或在viewModelScope使用 ViewModel 的情况下启动此等效代码:

viewModelScope.launch {
    try {
        someBgOperation()
    } catch (e: Exception){
        //show dialog
    }
}
Run Code Online (Sandbox Code Playgroud)

当活动关闭时,两个作用域都会自动取消,就像 Autodispose 所做的那样。但是 catch 块不仅会执行someBgOperation它自己抛出的正常错误,还会执行CancellationException引擎盖下的协程库用来处理取消的 s。如果我在活动关闭时尝试在那里显示一个对话框,我可能会收到新的异常。所以我被迫做这样的事情:

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

这感觉比 Rx 版本更冗长,并且它有一个空的 catch 子句,它会在 lint 输出中显示警告。在 try-catch 之后我做更多事情的其他情况下,我被迫从CancellationExceptioncatch返回以保持 UI 安全(这些返回是标记返回)。我发现自己一次又一次地重复这个丑陋的模板。

有没有更好的方法来忽略 CancellationException?

Rom*_*rov 6

我可以提出两种解决方案。首先,附加catch(e: CancellationException)条款看起来有点冗长。您可以将代码简化为:

viewModelScope.launch {
    try {
        someBgOperation()
    } catch (e: Exception) {
        if (e !is CancellationException) // show dialog
    }
}
Run Code Online (Sandbox Code Playgroud)

另一方面,您可以使用 Kotlin Flow,其catch运算符旨在为此而忽略取消。由于您实际上不会通过流程发送任何值,因此您应该使用Flow<Nothing>

flow<Nothing> {
    someBgOperation()
}.catch { e ->
    // show dialog
}.launchIn(viewModelScope)
Run Code Online (Sandbox Code Playgroud)


Ten*_*r04 5

编辑:修改,因为 CancellationExceptions 不应该被吞掉。

您可以创建一个转换为 a 的辅助函数Result,以便您只能处理非取消异常:

public inline fun <T, R> T.runCatchingCancellable(block: T.() -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        if (e is CancellationException) {
            throw e
        }
        Result.failure(e)
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

viewModelScope.launch {
    runCatchingCancellable {
        someBgOperation()
    }.onFailure { e ->
        //show dialog
    }
}
Run Code Online (Sandbox Code Playgroud)

此函数可以作为runCatching在可取消协程中使用的安全替代方案。

  • 您可以考虑使用 [`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-having.html) 构建它,而不是发明自己的包装器。 (2认同)

Ten*_*r04 2

我会考虑这个稍微干净的语法:

viewModelScope.launch {
    try {
        someBgOperation()
    } catch (e: Exception){
        if (isActive) {
            //show dialog
        }
    }
}
Run Code Online (Sandbox Code Playgroud)