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

时间:2019-11-07 17:52:12

标签: exception kotlin kotlin-coroutines

我试图了解Kotlin协程中的异常处理,所以我想到了一个非常简单的方案,其中网络调用引发异常,而我的应用必须捕获并处理它。

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

我在这里想念什么?

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

2 个答案:

答案 0 :(得分:2)

问题与您捕获异常的方式无关。问题在于,当异步作业失败(引发异常)时,它会取消您为活动所做的作业。

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

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

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

或者,如果您希望启动一种没有此问题的异步作业,请参见:Safe async in a given scope

答案 1 :(得分:1)

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

您使用async的动机是什么?在启动async和等待async之间,您打算同时做些什么?

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

如果您想在后台启动此任务,并从稍后调用的Android回调中等待它,则不能将其封装到单个工作单元中。您应该将异步任务附加到顶层SupervisorJob,其中应该包含class MyActivity : AppCompatActivity(), CoroutineScope by MainScope() { override fun onDestroy() { cancel() // cancel is extension on CoroutineScope } ... }

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

MainScope()

为了方便起见,Kotlin标准库添加了class Category(models.Model): name = models.CharField(max_length=256) class Meta: ordering = ('name',) def __str__(self): return self.name class Contact(models.Model): first_name = models.CharField(max_length=256) last_name = models.CharField(max_length=256) address = models.CharField(max_length=1024) city = models.CharField(max_length=256) country = models.ForeignKey(Country, on_delete=models.CASCADE) email = models.EmailField(max_length=256, unique=True) phone = models.CharField(max_length=256) category = models.ForeignKey(Category, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE) integration = models.ForeignKey(Integration, null=True, on_delete=models.SET_NULL) def __str__(self): return self.first_name class Company(models.Model): name = models.CharField(max_length=256) address = models.CharField(max_length=256) city = models.CharField(max_length=256) country = models.ForeignKey(Country, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): return self.name class ContactCompany(models.Model): contact = models.ForeignKey(Contact, on_delete=models.CASCADE, related_name='job') company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='company') job = models.TextField(blank=True, help_text='Job', max_length=5000, null=True) started_at = models.DateTimeField(auto_now_add=True) finished_at = models.DateTimeField(auto_now=True) def __str__(self): return self.job 委托,这样您就不会出错。