https://kotlinlang.org logo
Title
e

Eugen Martynov

01/26/2021, 5:28 PM
If I have a Cancellable implementation - can I somehow listen for the job cancellation and invoke cancel on Cancellable?
m

Marc Knaup

01/26/2021, 5:33 PM
Maybe
coroutineContext.job.invokeOnCompletion(onCancelling = true) { … }
🤔
a

Adam Powell

01/26/2021, 6:19 PM
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
suspendCancellableCoroutine { continuation ->
  val cancellable = startAsyncWork(..., Callback { result ->
    continuation.resume(result)
  })
  invokeOnCancellation {
    cancellable.cancel()
  }
}
m

Marc Knaup

01/26/2021, 6:21 PM
It’s experimental, not internal. But yeah, more context would be helpful to find a better approach.
a

Adam Powell

01/26/2021, 6:22 PM
@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

bezrukov

01/26/2021, 6:24 PM
Another option to achieve this via public api is to launch undispatched coroutine + infinity susped call + try catch
m

Marc Knaup

01/26/2021, 6:25 PM
@Adam Powell you’re right. I’ve mixed it up with something else. It’s internal.
a

Adam Powell

01/26/2021, 6:26 PM
@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

bezrukov

01/26/2021, 6:31 PM
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

louiscad

01/27/2021, 8:12 PM
try/catch/finally + awaitCancellation() is also an alternative.
e

Eugen Martynov

01/29/2021, 9:24 AM
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

bezrukov

01/29/2021, 9:42 AM
no, it's possible. Here is ext
fun CoroutineScope.invokeOnCancel(block: () -> Unit) {
    launch(start = CoroutineStart.UNDISPATCHED) { 
        try {
            suspendCancellableCoroutine<Unit> { }
        } finally {
            block()
        }
    }
}
then do
scope.invokeOnCancel { 
    // cleanup code here
}
e

Eugen Martynov

01/29/2021, 10:00 AM
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
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

bezrukov

01/29/2021, 10:18 AM
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