Kotlin协程异常处理——如何抽象try-catch
Kotlin coroutine exception handling - how to abstract the try-catch
我试图了解 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()
}
}
}
问题与您捕获异常的方式无关。问题是,当您的异步作业失败(抛出异常)时,它会取消您为 activity.
所做的作业
即使您的代码可以捕获异常并打印消息,父作业也会尽快终止。
而不是像这样:val: Job = Job()
,尝试val: Job = SupervisorJob()
主管作业在其子作业失败时不会被取消,因此这不会使您的应用程序崩溃。
或者,如果您想要一种启动没有此问题的异步作业的方法,请参阅:
要得到正确的解决方案,要解决的问题是使其与结构化并发的原则兼容。
您使用 async
的动机究竟是什么?在启动 async
和等待期间,您打算做什么?
如果async
启动和await
调用都是一个工作单元的一部分,async
调用的成功是整体成功的先决条件, 然后将整个工作单元包装在 coroutineScope
.
中
如果您想在后台启动此任务并等待稍后调用的 Android 回调,则无法将其封装到单个工作单元中。您应该将异步任务附加到顶层 CoroutineScope
,其中应该有一个 SupervisorJob
。
执行此操作的正确方法显示在 documentation of CoroutineScope
:
class MyActivity : AppCompatActivity(), CoroutineScope by MainScope() {
override fun onDestroy() {
cancel() // cancel is extension on CoroutineScope
}
...
}
Kotlin 标准库为方便起见添加了 MainScope()
委托,这样您就不会弄错了。
我试图了解 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()
}
}
}
问题与您捕获异常的方式无关。问题是,当您的异步作业失败(抛出异常)时,它会取消您为 activity.
所做的作业即使您的代码可以捕获异常并打印消息,父作业也会尽快终止。
而不是像这样:val: Job = Job()
,尝试val: Job = SupervisorJob()
主管作业在其子作业失败时不会被取消,因此这不会使您的应用程序崩溃。
或者,如果您想要一种启动没有此问题的异步作业的方法,请参阅:
要得到正确的解决方案,要解决的问题是使其与结构化并发的原则兼容。
您使用 async
的动机究竟是什么?在启动 async
和等待期间,您打算做什么?
如果async
启动和await
调用都是一个工作单元的一部分,async
调用的成功是整体成功的先决条件, 然后将整个工作单元包装在 coroutineScope
.
如果您想在后台启动此任务并等待稍后调用的 Android 回调,则无法将其封装到单个工作单元中。您应该将异步任务附加到顶层 CoroutineScope
,其中应该有一个 SupervisorJob
。
执行此操作的正确方法显示在 documentation of CoroutineScope
:
class MyActivity : AppCompatActivity(), CoroutineScope by MainScope() {
override fun onDestroy() {
cancel() // cancel is extension on CoroutineScope
}
...
}
Kotlin 标准库为方便起见添加了 MainScope()
委托,这样您就不会弄错了。