When using `suspendCancellableCoroutine` does the ...
# coroutines
j
When using
suspendCancellableCoroutine
does the order of the call to
invokeOnCancellation
matter? I mean, is it best to call
invokeOnCancellation
as first thing inside the
suspendCancellableCoroutine
lambda? Or is it the same if it's called as last instruction?
1
b
my opinion it needs to be at the end of the lambda. Otherwise, technically you may try to clean up resources (e.g. remove the listener) before allocating them (e.g. adding a listener) because technically continuation might be cancelled right in the beginning of the lambda
https://pl.kotl.in/LQdCxvgIW Better to see it in the example (hit 'Run' button in the top-right corner)
z
Yes the docs mention that:
When the continuation is already cancelled, the handler is immediately invoked with the cancellation exception.
So if you called it before setting a listener, you’d leak the listener. Also note that the cancellation callback is not dispatched, it’s ran synchronously, so if your listener api requires the listener be removed on a certain thread or to be otherwise synchronized you’d need to do that explicitly. Often more correct to remove the listener in a finally block around the suspension point, since that will be dispatched.
👍 1
j
Oh nice thanks, where is "around the suspension point" in this case?
b
Sometimes it is possible, sometimes it is not (when the cleanup requires the listener) Typically it looks like:
Copy code
try {
   suspendCancellableCoroutine { c -> ....
   }
} finally { 
   cleanup()
}
When it is not possible with suspendCancellableCoroutine (e.g. your listener needs a continuation so it has to be initialized within lambda, and cleanup requires a listener outside of the lambda) it is possible to do with completableDeferred
j
so in this context what Zach called "the suspension point" is the call to
suspendCancellableCoroutine
?
b
yes
👌 1
j
Just to understand the inner workings a bit more... In Zach's example he says the content of the
finally
block will be dispatched. What is it that actually makes it dispatched? Is it some special "magic" around this specific
try/finally
case or is it just that any statement happening after a suspend call is dispatched separately?
b
> or is it just that any statement happening after a suspend call is dispatched separately? Yes, the code inside a coroutine is executed in the dispatcher from coroutine's context before and after a suspension point. e.g. in
Copy code
withContext(Dispatchers.Main) { 
   print("A") 
   delay(300)
   print("B")
}

or
withContext(Dispatchers.Main) { 
   print("A")
   suspendCancellableCoroutine { .... }
   print("B")
}

or
withContext(Dispatchers.Main) {
   print("A")
   withContext(Dispatchers.Default) { ... }
   print("A")
}
Both A and B are guaranteed to be executed in Main thread. Nothing special about try/finally here, try/finally in this pattern is just to ensure resources are cleaned up in any case (successful completion / cancellation / error) In contrast, in the following example:
Copy code
withContext(Dispatchers.Main) { 
   print("A")
   suspendCancellableCoroutine { cont -> 
      print("B")
      cont.invokeOnCancellation { print("C") }
   }
   print("D")
}
C - might be invoked in random thread (typically in the thread that cancels the parent coroutine). A,B,D are executed in Main
🙌 1
j
Great explanation, thanks, this really makes it clear!
116 Views