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

natario1

12/10/2023, 11:25 AM
Hi guys, could someone explain these lines in the CoroutineDispatcher docs?
In order to execute a block in place, it is required to return false from
isDispatchNeeded
and delegate the
dispatch
implementation to
Dispatchers.Unconfined.dispatch
in such cases.
I know that if I return false from
isDispatchNeeded
,
dispatch
should not even be called (except maybe for yield?). So in which case should one delegate to
Dispatchers.Unconfined.dispatch()
? This suggestion is mentioned in the docs as something important but I haven’t seen it done in any open source implementation.
s

Sam

12/11/2023, 10:32 AM
You're right,
yield()
is the reason for this rule. From the docs for `yield()`:
If the coroutine dispatcher is
Unconfined
, this functions suspends only when there are other unconfined coroutines working and forming an event-loop. For other dispatchers, this function calls
dispatch
and always suspends to be resumed later regardless of the result of
isDispatchNeeded
. If there is no
CoroutineDispatcher
in the context, it does not suspend.
We're dealing with a performance optimisation on top of a performance optimisation here. 1. If you're already on the correct thread, you can return false from
isDispatchNeeded()
to indicate that the continuation can run in-place to avoid the cost of a dispatch. 2. But, if the continuation is a result of a call to
yield()
, the continuation should not run in-place, because the user explicitly indicated that other coroutines should get a chance to run first. This is why
dispatch
is always called for a
yield()
, even when
isDispatchNeeded()
is
false
. 3. But, if there are no other queued coroutines,
yield()
may still perform an immediate dispatch. This is the reason that
Dispatchers.Unconfined
has a funky implementation of
dispatch
that effectively tells the continuation "no, really, do the immediate dispatch anyway". You cannot do this from your own dispatcher implementations, because you're forbidden from performing an immediate dispatch from the
dispatch
method. The final optimisation (3) is the only thing you gain by delegating to
Dispatchers.Unconfined
. It allows you to perform an immediate dispatch during a
yield()
if you identify that there are no other coroutines that you can schedule instead.
It's fine if you don't do it. You'll just get a slight performance hit on calls to
yield()
, because it will always dispatch even when there's nothing to yield to.
n

natario1

12/11/2023, 2:49 PM
Thanks for the insight Sam! Very interesting. I still don’t understand how to implement it though (I’m not planning to do it tbh, just curious). Should one do: override fun dispatch(ctx, runnable) { if (!isDispatchNeeded(ctx)) { Dispatchers.Unconfined.dispatch(ctx, runnable) } else { … } } Or what. The code above would call “isDispatchNeeded” twice for most continuations, which sucks. Or one can check whether it is a yield call from the context. Either way the docs I pasted are pretty confusing, no?
s

Sam

12/11/2023, 2:53 PM
Yeah, the code you shared seems to be what the docs are suggesting. I agree, it's confusing. I can see why none of the currently-implemented immediate dispatchers (Android, Swing, etc.) seem to bother with it.
👍 1