Matt Timmermans
Matt Timmermans

Reputation: 59144

Safe async in a given scope

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:

Upvotes: 5

Views: 741

Answers (1)

Rene
Rene

Reputation: 6148

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
}

Upvotes: 3

Related Questions