is there an example anywhere showcasing the differ...
# coroutines
u
is there an example anywhere showcasing the difference between
suspendAtomicCancellableCoroutine
and
suspendCancellableCoroutine
?
j
My understanding is that the atomic version, ensure that it will be not be cancellable after a call of
cont.resume()
, while the other may be cancelled even after calling
cont.resume()
.
u
Hm, yeah. But still kinda feel we need some practical examples, like, when should I choose which variant.
j
Here is my understanding of the concept. It may be wrong. If so, I would be pleased to read corrections. Before talking about atomicity in cancellation of coroutines, let's look at what atomicity is.
Copy code
val atomicValue = AtomicInteger(0)
var value = 0

fun usage() {

	// This doesn't ensure that the value printed is the one after increment
	// Because another thread could have changed the value in between (it is not atomic)
	++value
	println(value)
	
	// This ensure to print the correct value after increment (it is atomic)
	println(atomicValue.incrementAndGet())
}
I think it is the same for coroutines cancellation
Copy code
suspend fun atomicCancellableFct() =
	suspendAtomicCancellableCoroutine<Unit> { it.resume(Unit) }

suspend fun cancellableFct() = 
	suspendCancellableCoroutine<Unit> { it.resume(Unit) }

suspend fun usage() {

	// This call may throw a CancellationException, even if the actual content has been resumed
	// This is because cancel may be called during the resume process
	cancellableFct()
	
	// This ensure that either 
	// * the content is resumed but doesn't throw any CancellationException
	// * the content is NOT resumed and throw a CancellationException
	// But never both, because it will be *atomic*
	atomicCancellableFct()
}
And about how to choose, for me it is the same as choosing between
Int
and
AtomicInteger
. I will always use non-atomic operations unless I am implementing a very specific case that does need to use atomicity.
u
so it's not an implementation detail of the side using `suspendAtomicCancellableCoroutine`/`suspendCancellableCoroutine` but the client needs to actually know this
which... I dunno, feels like a weird coupling to me
j
A concrete example is the function
lock()
of
Mutex
. The cancellation of this function is atomic, and the reason is quite easy to understand I think. We need to be sure that either the function throw a
CancellationException
OR a lock has been acquired, but not both. If the function would succeed to acquire a lock AND throw an exception, one could end up with deadlocks very difficult to debug.
Two examples:
delay()
cancellation is not atomic, because in case of cancellation, we don't care if the delay already resumed or not.
Mutex.lock()
cancellation is atomic. Because in case of cancellation, if a lock has been acquired, we have to call
Mutex.unlock()
to avoid deadlocks.
Bottom line: If you need to ensure the caller that either the job is done or cancelled but not both, use
suspendAtomicCancellableCoroutine
, otherwise, use
suspendCancellableCoroutine
👍 1
u
sounds good
thanks for the help @Jonathan
u
Wow, nice explanations @Jonathan
1