我试图了解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()
}
}
}
答案 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
委托,这样您就不会出错。