https://kotlinlang.org logo
Title
d

David Glasser

08/27/2019, 10:44 PM
I'm trying to understand the difference between
suspendCoroutine
and
suspendCancellableCoroutine
when writing a wrapper around an external callback-based API. My original understanding was that the difference between the two is just whether or not the continuation you get access to in the block has methods that lets the block you write right there cancel it. So you should use the latter if the API you're wrapping has some way of indicating that it cancelled so that you would want to cancel the suspended coroutine, but otherwise there's no need to use the longer-named function. But when I test these out,
fun main() = runBlocking {
	withTimeout(10) {
		suspendCancellableCoroutine { continuation: CancellableContinuation<Unit> -> }
	}
}
throws a TimeoutCancellationException (as I expected), but
fun main() = runBlocking {
	withTimeout(10) {
		suspendCoroutine { continuation: Continuation<Unit> -> }
	}
}
appears to hang, which surprises me. Does that imply that when you run suspendCoroutine then your current coroutine can't be cancelled "from above"? In that case, why would you ever use suspendCoroutine instead of suspendCancellableCoroutine?
o

octylFractal

08/27/2019, 10:50 PM
suspendCoroutine
doesn't check for cancellation at all, unless you do it within the block itself using `isActive`/calling a cancellable suspend point. The reason there are both, is that cancellation isn't a concept in the stdlib coroutines, that's purely part of the kotlinx coroutines library, along with Job, CoroutineScope, etc.
to be clear,
suspendCoroutine
is useful if you want to suspend in a non-Job scope, such as with
sequence {}
d

David Glasser

08/27/2019, 10:56 PM
Ah, I see. But most of the time you want to use suspendCancellableCoroutine?
o

octylFractal

08/27/2019, 10:58 PM
Probably, since offering as many cancellation points as possible is a good thing
z

Zach Klippenstein (he/him) [MOD]

08/28/2019, 5:10 PM
whether or not the continuation you get access to in the block has methods that lets the block you write right there cancel it.
It’s the other way around –
suspendCancellableCoroutine
lets you notify the API you’re wrapping that the coroutine was cancelled (via its Job). That’s why your second example hangs –
withTimeout
tries to cancel the coroutine, which is suspended, but since you haven’t opted into cancellation it can’t be cancelled.
d

David Glasser

08/28/2019, 5:48 PM
@Zach Klippenstein (he/him) [MOD] so that's what I thought sCC was for — telling a wrapped API to stop when cancelled. Which made me think "if the wrapped API doesn't have a way to be cancelled and we just have to abandon it to keep running in its own non coro thread, then just use sC instead of sCC". but that isn't what seems to happen?
z

Zach Klippenstein (he/him) [MOD]

08/28/2019, 6:01 PM
Ah, I misunderstood your initial message.
I think your example is hanging because
runBlocking
will block until all child coroutines have finished, and since your (uncancellable) coroutine never finishes, it never returns.