协程中未捕获异常

时间:2019-05-22 06:16:36

标签: android kotlin kotlin-coroutines

我似乎无法在协程中完成错误处理。我已经读了很多文章和exception handling documentation,但似乎无法正常工作。

这是我的设置:

我的ViewModel用它的范围启动协程

class MyViewModel(private var myUseCase: MyUseCase) : ViewModel() {
    private val viewModelJob = Job()
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

    fun doSomething() {
        uiScope.launch {
            try {
                myUseCase()
            } catch (exception: Exception) {
                // Do error handling here
            }
        }
    }
}

我的UseCase仅处理一些逻辑,在这种情况下,是某种验证器

class MyUseCase(private val myRepository: MyRepository) {
    suspend operator fun invoke() {
        if (checker()) {
            throw CustomException("Checker Failed due to: ...")
        }

        myRepository.doSomething()
    }
}

然后我的Repository仅处理网络层/本地层

object MyRepository {
    private val api = ... // Retrofit

    suspend fun doSomething() = api.doSomething()
}

这是我的Retrofit界面

interface MyInterface {
    @POST
    suspend fun doSomething()
}

来自ViewModel的try / catch可以处理Retrofit调用中的错误,但是,不能捕获来自CustomException的{​​{1}}中的错误。从我一直阅读的文章来看,这应该可行。如果我使用UseCase,我可以做async并消除错误,但是在这种情况下,我不必使用await,而我一直在解决这个问题。我可能会迷路。

任何帮助将不胜感激!预先感谢!

编辑:

这是我得到的错误日志:

async

错误直接指向显式com.example.myapp.domain.errors.CustomException at com.example.myapp.domain.FeatureOne$invoke$2.invokeSuspend(FeatureOne.kt:34) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594) at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742) 语句。

3 个答案:

答案 0 :(得分:3)

尝试使用CoroutineExceptionHandler是解决协程内部处理异常的方法。

CoroutineExceptionHandler context 元素用作协程的通用catch块,可以在其中进行自定义日志记录或异常处理。类似于使用Thread.uncaughtExceptionHandler

如何使用它?

val handler = CoroutineExceptionHandler { _, exception -> 
    println("Caught $exception") 
}
val job = GlobalScope.launch(handler) {
    throw AssertionError()
}
val deferred = GlobalScope.async(handler) {
    throw ArithmeticException() // Nothing will be printed, relying on user to call 
    deferred.await()
}
joinAll(job, deferred)

在您的ViewModel中,确保您的uiScope使用SupervisorJob而不是JobSupervisorJob可以单独处理其子项的失败。 Job将被取消,而不像SupervisorJob

如果您将2.1.0用于AAC Lifecycle和ViewModel,请改用viewModelScope扩展名。

答案 1 :(得分:0)

据我所知,Retrofit仍然没有创建使用suspend关键字标记方法的方法。您可以参考此link。 因此,您的MyInterface的正确方法是:

interface MyInterface {
    @POST
    fun doSomething(): Deferred<Response<YourDataType>>
}

答案 2 :(得分:0)

解决此问题的另一种方法是隐藏您的自定义错误对象以实现CancellationException

例如:

您的CustomException可以实现为:

sealed class CustomError : CancellationException() {
        data class CustomException(override val message: String = "Checker Failed due to: ...") : CustomError
}

此异常将被捕获在视图模型的try / catch块中