https://kotlinlang.org logo
#coroutines
Title
# coroutines
d

dave08

01/02/2020, 2:28 PM
In
callbackFlow
, when should I use
cancel(CancellationException("....", e))
and when should I just throw the exception? Is there any difference?
d

Dominaezzz

01/02/2020, 2:43 PM
I'm pretty sure just throwing the exception, will end up calling
cancel
somewhere internally (the channel has to be released somehow, right?). I guess calling
cancel
is a bit better since you can specify more info about cancellation with a string.
d

dave08

01/02/2020, 2:55 PM
That's what I was thinking... 😉, just was wondering if there was a better reason since the example in the function docs uses that... It also feels a bit funny to create the
CancellationException
there, shouldn't
cancel
just recieve its parameters directly...? It seems like that function only takes in a
CancellationException
anyways...
a

Adam Powell

01/02/2020, 3:59 PM
this line of questioning implies a potential misunderstanding about cancellation vs. errors
callbackFlow
uses a
ProducerScope
that is backed by a channel. Your
callbackFlow
is the sender and the downstream collector is the receiver
if an error is being reported to your callback and you want to report it downstream, you should do so using
close(cause: Throwable)
, not cancel.
the
cancel
method is in scope as an extension for
CoroutineScope
- the scope that you can launch into for parallel decomposition, etc. It's not for reporting errors. That's why it restricts you to
CancellationException
only.
Very nice explanation by the way... so really cancel would just cause the upstream's scope to be cancelled, but wouldn't
close
be called anyways on the channel in such a case?
a

Adam Powell

01/02/2020, 4:15 PM
🤔 @Vsevolod Tolstopyatov [JB] what was the intention there? That looks like a very strange example. Considering an API call failure to be cancellation rather than an error in a flow doesn't seem right.
@dave08 yes, (had to check the source to be sure of the exact mechanism just in case 🙂 ) as a producer coroutine scope is cancelled it closes the channel
d

dave08

01/02/2020, 4:33 PM
And closing the channel cancels the scope?
If it works either way, then it would just be an issue of semantics...
Third possibility: just throw an exception... it'll probably also cancel the scope and close the channel... 🤒
a

Adam Powell

01/02/2020, 4:40 PM
it looks like closing the channel doesn't cancel the scope, the dependency is the other way around for producers
d

dave08

01/02/2020, 4:41 PM
And just throwing an exception?
a

Adam Powell

01/02/2020, 4:41 PM
and the question is where do you throw the exception from? If you're in a callback that's receiving an exception as a parameter, you're on the call stack of something not in a coroutine. Throwing will just throw to the callback's caller, not to the flow
that's why you can
close(e)
to the `callbackFlow`'s channel instead - that will make the receiver (the flow) resume with the close exception
and it won't lose the other objects you sent/offered to the channel ahead of it
d

dave08

01/02/2020, 4:43 PM
Very good point... but you mean
cancel
since it closes the channel too, or do you still think
close
should be used?
a

Adam Powell

01/02/2020, 4:43 PM
close
. When it comes to channels
close
is the sender-side operation,
cancel
is the receiver-side operation.
cancel
on a channel means, "I cannot, will not receive any more items, let the sender know, and any still lingering in the buffer will be lost."
❤️ 1
close
means, "here's the end of this sequence of items and possibly a throwable cause if the end-of-sequence is happening for an exceptional reason." All of the items sent to the channel before it will still be received before the close signal is.
❤️ 1
d

dave08

01/02/2020, 4:46 PM
But here, we're talking about the
cancel
on the
ProducerScope
not the
cancel
on the actual channel...?
a

Adam Powell

01/02/2020, 4:48 PM
that's right. Since
ProducerScope
implements
SendChannel
but not
ReceiveChannel
, the channel-cancel method isn't even available
only the convenience method to cancel a whole
CoroutineScope
, which is more often used when cancelling a scope externally, e.g.
Copy code
val myScope = CoroutineScope(Dispatchers.Main + Job())
// ...
override fun onDestroy() {
  myScope.cancel()
}
d

dave08

01/02/2020, 4:51 PM
Oh... I think things are clearer now, thanks for this explanation! So really this should never be called in a
callbackFlow
...
Since the receiver in a
Flow
doesn't even have access to that scope anyways, so nobody's there to say "I can't receive things anymore" in that particular way.
a

Adam Powell

01/02/2020, 4:53 PM
I wouldn't, anyway. Even outside of the case of flows and channels I haven't found much reason to use that scope extension from inside the scope of its receiver vs. simply throwing a
CancellationException
. You immediately end up in cooperative cancellation limbo where
isActive
is now false but your code in a cancelled job is still running
👍🏼 1
another useful thought experiment here is this,
Copy code
try {
  myFlow.collect {
    // ...
  }
} catch (???) {
  // ???
}
what do you want your flow's users to write in the
???
spots?
how should they handle errors produced by your flow? You probably don't want them to have to catch
CancellationException
, check the
cause
, then re-throw if it's a "real" coroutine cancellation vs. a reported error from your flow, you want them to be able to catch your exception type
and
close(e)
gives you that
(also thanks to some debugging-related things coroutines do with exception cloning and causes to try to provide more helpful stack traces, checking the cause of a
CancellationException
like that has major additional issues - you might have to unwrap several layers)
d

dave08

01/02/2020, 5:38 PM
Well there's the
catch { }
operator... which probably gives that
CancellationException
too with
cancel
🤕... whereas with
close
it would just give
e
. But you're saying with
collect()
the
close(e)
(even with the
Exception
parameter), wouldn't throw a
CancellationException
, it would just throw the
e
itself... This is also a good point, I guess that example in the repo is wrong...
a

Adam Powell

01/02/2020, 5:41 PM
yeah that example has me scratching my head, it doesn't result in code I would want to be the caller of
and depending on how other flow operators in the chain look, it might even be possible to construct a chain where the cancellation isn't reported downstream at all and is treated as a successful end-of-flow instead, if it ends up being collected as part of some internal child job
46 Views