If I have a Cancellable implementation - can I som...
# coroutines
e
If I have a Cancellable implementation - can I somehow listen for the job cancellation and invoke cancel on Cancellable?
m
Maybe
Copy code
coroutineContext.job.invokeOnCompletion(onCancelling = true) { … }
🤔
a
iirc that api is considered private/internal, and it's probably not what you want anyway. Can you explain a bit more about what you're trying to do?
generally things with this pattern are cases where you want to suspend and await completion, but you want to propagate cancellation. So it ends up being something like
Copy code
suspendCancellableCoroutine { continuation ->
  val cancellable = startAsyncWork(..., Callback { result ->
    continuation.resume(result)
  })
  invokeOnCancellation {
    cancellable.cancel()
  }
}
m
It’s experimental, not internal. But yeah, more context would be helpful to find a better approach.
a
Copy code
@InternalCoroutinesApi
    public fun invokeOnCompletion(
        onCancelling: Boolean = false,
        invokeImmediately: Boolean = true,
        handler: CompletionHandler): DisposableHandle
so optin-internal rather than keyword-
internal
, but not really experimental in the sense of, "this is on track to become public API in the future" either
the variant that runs when the job is complete (children have joined rather than just point where cancellation is initiated) is public though
b
Another option to achieve this via public api is to launch undispatched coroutine + infinity susped call + try catch
m
@Adam Powell you’re right. I’ve mixed it up with something else. It’s internal.
a
@bezrukov yes, but if you're going to do that you might as well do it via
suspendCancellableCoroutine
and
CancellableContinuation.invokeOnCancellation
. Using try/catch means your continuation needs to resume on its dispatcher before the cleanup code runs. Generally there's no reason to wait for that unless cancellation requires you to be on a particular dispatcher to perform it
e.g. main/UI thread requirements or similar
b
suspendCancellableCoroutine
works for "cold" Closeable (disposable/future/whatever). If you need to close hot closeable once job cancelled, suspendCancellableCoroutine won't work, because it checks cancellation before
l
try/catch/finally + awaitCancellation() is also an alternative.
e
Sorry for the delay and thank you for the answers
I have a runtime class that accept messages. runs update of the state from that message and executes effect that will produce another message.
It is run on three different contexts - runtime, render and effect
The runtime by itself is coroutine scope
and all launch is happening as a child of it
but it is not important
The runtime accepts listeners and I want to remove listener as soon as context is cancelled
But looks from you answers it is not possible
b
no, it's possible. Here is ext
Copy code
fun CoroutineScope.invokeOnCancel(block: () -> Unit) {
    launch(start = CoroutineStart.UNDISPATCHED) { 
        try {
            suspendCancellableCoroutine<Unit> { }
        } finally {
            block()
        }
    }
}
then do
Copy code
scope.invokeOnCancel { 
    // cleanup code here
}
e
Interesting, let me digest this code first. Can you recommend something good about coroutines mechanics, where is callback put, on which thread it operated. I also never saw
Copy code
suspendCancellableCoroutine
So if I understand this code correctly, we launch some empty coroutine that will be cancelled only when scope is cancelled
and then we can hock to it and run some code that we want
b
yes,
suspendCancellableCoroutine<Unit> { }
can be changed to
delay(Long.MAX_VALUE)
if it more descriptive
where is callback put, on which thread it operated.
If the scope was already cancelled, callback will be invoked immediately on the caller thread. Otherwise it will be invoked on scope's dispatcher. If you want it to be invoked on scope's dispatcher (even if it's already cancelled), use
CoroutineStart.ATOMIC