问题
I want to start an async coroutine with a suspending function in a given parent CoroutineScope
, to produce a Deferred
that might then be used from any coroutine in that scope.
I would like its job to be cancelled if the parent's job is cancelled, but if the suspending function throws an exception, I need that captured in the resulting Deferred
without cancelling sibling jobs in the parent scope.
The way I'm doing it works fine, but I'm wondering if there's a simpler, more idomatic way than this:
fun <T> callIt(scope: CoroutineScope, block: suspend () -> T) : Deferred<T> {
val result = CompletableDeferred<T>()
scope.launch {
try {
result.complete(block())
} catch (e: Throwable) {
result.completeExceptionally(e)
}
}
return result
}
I like that the handling of exceptions from the suspending block
is obviously what I want, but I'm not too happy about building an async
out of launch
Things that don't work:
- An async job with an exception handler.
async
catches its exceptions, but the job still fails and cancels its parent. As @Rene commented: The documentation ofasync
says: " it cancels the parent job (or outer scope) on failure to enforce structured concurrency paradigm.".
回答1:
You can do it without a CompletableDeferred
. You need to create an SupervisorJob and let the job from the CoroutineScope
be the parent of the new job. If the scope is cancelled the async
-coroutine is cancelled as well. But no exception inside the async
-coroutine will cancel the parent scope.
Because of an open issue https://github.com/Kotlin/kotlinx.coroutines/issues/1578 we need to explicitly complete the SupervisorJob.
fun <T> callIt(scope: CoroutineScope, block: suspend () -> T): Deferred<T> {
val supervisorJob = SupervisorJob(scope.coroutineContext[Job])
val deferred = scope.async(supervisorJob) {
block()
}
deferred.invokeOnCompletion {
supervisorJob.complete()
}
return deferred
}
来源:https://stackoverflow.com/questions/58229357/safe-async-in-a-given-scope