繁体   English   中英

Kotlin协程异常处理——如何抽象try-catch

[英]Kotlin coroutine exception handling - how to abstract the try-catch

我试图了解 Kotlin 协程中的异常处理,所以我想出了这个非常简单的场景,其中网络调用引发异常,我的应用程序必须捕获并处理它。

如果我用 try-catch 块包围我的 async.await() 调用,它会按预期工作。 但是,如果我尝试将 try-catch 抽象为扩展 function,我的应用程序将崩溃。

我在这里想念什么?

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

class Main2Activity : AppCompatActivity() {

    private val job: Job = Job()
    private val scope = CoroutineScope(Dispatchers.Default + job)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        runCode()
    }

    private suspend fun asyncCallThrowsException(): Deferred<Boolean> =
        withContext(Dispatchers.IO) {
            Thread.sleep(3000)// simulates a blocking request/response (not on the Main thread, though)
            throw(Exception())
        }

    suspend fun <T> Deferred<T>.awaitAndCatch() {
        try {
            this.await()
        } catch (e: Exception) {
            println("exception caught inside awaitAndCatch")
        }
    }

    private fun runCode() {
        scope.launch {

            //This block catches the exception.
            try {
                val resultDeferred = asyncCallThrowsException()
                resultDeferred.await()
            } catch (e: Exception) {
                println("exception caught inside try-catch")
            }

            //This line does not, and crashes my app.
            asyncCallThrowsException().awaitAndCatch()
        }
    }
}

编辑:我实际上忘记将调用包装在async块中。 现在,即使是明确的 try-catch 块也不起作用......

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

class Main4Activity : AppCompatActivity() {

    private val job: Job = Job()
    private val scope = CoroutineScope(Dispatchers.Default + job)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        runCode()
    }

    private suspend fun callThrowsException(): String =
        withContext(Dispatchers.IO) {
            Thread.sleep(3000)// simulates a blocking request/response (not on the Main thread, though)
            throw(Exception())
            "my result"
        }

    suspend fun <T> Deferred<T>.awaitAndCatch(): T? {
        try {
            return this.await()
        } catch (e: Exception) {
            println("exception caught inside awaitAndCatch")
        }
        return null
    }

    private fun runCode() {
        scope.launch {

            val resultDeferred: Deferred<String> = async { callThrowsException() }
            var result: String?

//            This doesn't catch the throwable, and my app crashes - but the message gets printed to the console.
            try {
                result = resultDeferred.await()
            } catch (e: Exception) {
                println("exception caught inside try-catch")
            }

//            This doesn't catch the throwable, and my app crashes - but the message gets printed to the console.
            result = resultDeferred.awaitAndCatch()
        }
    }
}

问题与您如何捕获异常无关。 问题是,当您的异步作业失败(抛出异常)时,它会取消您为活动所做的作业。

即使您的代码可以捕获异常并打印消息,父作业也会尽快终止。

不要像这样: val: Job = Job() ,尝试val: Job = SupervisorJob()

当其子项失败时,主管作业不会被取消,因此这不会使您的应用程序崩溃。

或者,如果您想要一种启动不存在此问题的异步作业的方法,请参阅: Safe async in a given scope

要找到正确的解决方案,要解决的问题是使其与结构化并发的原则兼容。

你使用async的动机到底是什么? 在启动async和等待它之间,您打算在此期间做什么?

如果async启动和await调用都是单个工作单元的一部分,并且async调用的成功是整体成功的先决条件,则将整个工作单元包装在coroutineScope中。

如果您想在后台启动此任务并从稍后调用的 Android 回调中等待它,则无法将其封装到单个工作单元中。 您应该将异步任务附加到顶级CoroutineScope ,其中应该有一个SupervisorJob

CoroutineScope的文档中显示了执行此操作的正确方法:

class MyActivity : AppCompatActivity(), CoroutineScope by MainScope() {
    override fun onDestroy() {
        cancel() // cancel is extension on CoroutineScope
    }

    ...
}

为了方便起见,Kotlin 标准库添加了MainScope()委托,因此您不会弄错。

原因很简单。 在这行代码中,没有围绕try-catch asyncCallThrowsException()内部抛出的异常的 try-catch:

asyncCallThrowsException().awaitAndCatch()

如果你在这一行设置断点:

throw(Exception())

你会看到,如果你向上移动调用堆栈,调用链不包含try-catch

您似乎认为asyncCallThrowsException()是异步运行的,但是您必须使用async 像这样:

    private fun asyncCallThrowsException(): Deferred<Boolean> = async(Dispatchers.IO) {
        Thread.sleep(3000)// simulates a blocking request/response (not on the Main thread, though)
        throw(Exception())
    }

在此更改之后,您的try-catch助手 function 应该会生效。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM