Kotlin协程吞噬异常

5
我对协程中的异常处理方式感到很困惑。我希望能够有一系列的挂起函数,像同步代码一样在它们之间传递异常。因此,如果例如 Retrofit 抛出了 IOException 异常,我可以在挂起函数链的开头处处理该异常,比如在 presenter 中向用户展示错误信息。
我制作了一个简单的示例来尝试协程,但是如果我取消注释 throw Exception 调用中的任何一个,那么异常后面的代码就无法运行,但是异常并不会导致应用程序崩溃。
package com.example.myapplication

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.Button
import android.widget.TextView
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch

class MainActivity : AppCompatActivity() {

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

        val text = findViewById<TextView>(R.id.thing_text)
        val button = findViewById<Button>(R.id.thing_button)

        var count = 0

        button.setOnClickListener {
            launch {
                count++
//                throw Exception("Boom")
                val string = delayedStringOfInt(count)
                runOnUiThread { text.text = string }
            }
        }
    }

    suspend fun delayedStringOfInt(int: Int): String {
        delay(1000)
//        throw Exception("Boom")
        return int.toString()
    }
}

我已尝试使用 asyncCoroutineExceptionHandler

2个回答

2

在使用async时,你应该在某个地方await结果,这样就不会丢失任何异常。


1
我之前尝试过使用asyncawait,但是它会抱怨Suspend function await should only be called from a coroutine or another suspend function.。然而,将所有内容包装在runBlocking中可以正常运行代码的异步执行。runBlocking是一个令人困惑的名称,因为我期望它会阻塞直到内部的协程完成。在我的情况下,如果您快速点击按钮,我希望每秒钟都会得到递增的数字,而不是快速递增。我会将其标记为答案,但是将我的修复代码放在另一个答案中。 - TTransmit
仔细观察后,我发现在这种情况下runBlocking正在阻塞UI线程,这不是我想要的。我将尝试使用回调函数。 - TTransmit
在这里使用kotlinx Continuation目前只会给我带来丑陋的回调地狱。 - TTransmit

1
这里是基于Alexey Romanov的答案捕获异常的代码。经过一些修改,我已经让它能够在启动时工作了。添加Log.d("thread", Thread.currentThread().name)可以显示延迟不会阻塞UI。
class MainActivity : AppCompatActivity() {

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

        val text = findViewById<TextView>(R.id.thing_text)
        val button = findViewById<Button>(R.id.thing_button)

        var count = 0

        button.setOnClickListener {
            launch {
                val job = async {
                    count++

                    val string = delayedStringOfInt(count)
                    updateTextView(text, string)
                }

                try {
                    job.await()
                } catch (e: IOException) {
                    makeToastFromException(e)
                }
            }
        }
    }

    fun makeToastFromException(e: Exception) {
        runOnUiThread {
            Toast.makeText(this@MainActivity, e.localizedMessage, Toast.LENGTH_SHORT).show()
        }
    }

    fun updateTextView(text: TextView, string: String) {
        runOnUiThread { text.text = string }
    }

    suspend fun delayedStringOfInt(int: Int): String {
        delay(2000)
        if (int % 4 == 0) throw IOException("Boom")
        return int.toString()
    }
}

这不会打印异常信息。 - IgorGanapolsky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接