When writing a suspend function that will do work ...
# coroutines
c
When writing a suspend function that will do work off of the main thread, it looks like withContext() is recommended (as seen by Romans article here)
To make everybody’s life simpler we use the following _convention_: suspending functions do not block the caller thread.
The means to implement this convention are provided by the
withContext
function.
One thing that still hasn't clicked with me yet is when to use withContext vs launch vs async. I feel like I've basically always just used launch, but have been using withContext as of late, but dont really know why. Any quick tips/rules you use when you're writing kotlin code on when to do what? Specifically what sparked this question was how to make certain functions I'm writing more testable.
k
withContext
will suspend the caller until the work inside the context switch is done.
launch
and
async
are explicitly created to run things concurrently. They are different use cases. The question really comes down to whether you want to have multiple things run concurrently (launch/async) or whether you want to suspend the caller while you switch the context of execution for some block of code (withContext).
x
launch
and
async
are 2 of the 3 coroutine builders (together with
runBlocking
). Neither of them are suspend functions and they're the starting point to get you into the coroutines world.
withContext
is a
suspend fun
that can only be called once on the coroutines world. It will suspend the coroutine from which it was called and inmediately start a new coroutine with the provided
CoroutineContext
. It overrides the
Job
of the parent coroutine by default. This means that
withContext(<http://Dispatchers.IO|Dispatchers.IO>)
is doing something similar to
withContext(<http://Dispatchers.IO|Dispatchers.IO> + Job())
y
Isn't withContext basically a noop if you are on the right threads?
And even safe for Default -> IO?
x
Isn't withContext basically a noop if you are on the right threads?
Not really, even if you do
withContext(currentCoroutineContext())
, you are still suspending one coroutine and launching another in the same context. Whenever a coroutine is suspended, it may restart in another thread of the same pool of theads (unless the case of
Dispatchers.Main
, as for that case there's only one thread and the coroutine will resume its execution on it)
s
Here’s the way I like to think about it: •
withContext
and
coroutineScope
are foreground coroutine builders. They start a new job as a child of the current job, and then wait for it and all its children to complete before returning. •
launch
and
async
are background coroutine builders. They start a new child job and then return immediately, without waiting for the new job to complete.
🆒 1
👍 1
By the way, a quick clarification on
withContext(Job())
. A coroutine builder always creates a new job, as a child of the job in its context. By default, a coroutine builder inherits the context from its containing scope. Passing any context elements to the builder replaces the ones inherited from the scope. But the builder will still create a new job, as a child of whatever job was passed to it. The result is that passing a job to
withContext()
(or any coroutine builder) basically detaches it from its containing scope, breaking the structured concurrency job hierarchy. Passing a job to a a coroutine builder is generally a bad idea and should be avoided.
x
Great point @Sam! I was trying to somewhat make the explanation the most explicit possible, but your point here is key for correctness
c
Interesting! I'm starting to wonder why I've so infrequently used
withContext
as I've been using coroutines for a while but only now have I started using it to wrap around my database operations. I wonder if it's just because most coroutine-y work that I've done depended on other libraries that were already main safe (i.e. retrofit)
It seems like most functions that I write that are doing IO or computation should basically just be wrapped with
withContext
and then in my calling code (app code) that's where I would make the decision to wrap it in launch or async if needed.
👍 4
3
k
Yup. Suspend functions give you the power to maybe do something concurrently at call site. Always starting a coroutine does not give you that option.
😍 1
c
Going to write this "rule" in my teams readme. Sound pretty ok to everyong? "If you have a function that does something potentially "thread blocking" like io or computation, then mark it as suspend and wrap the function block withContext() and pass in a context to allow it to be tested"
👍 1
👌 3