what are the rules of thumb about when to make a f...
# coroutines
t
what are the rules of thumb about when to make a function
suspend
or just call a coroutine from within that method?
s
A
suspend
function returns a result, waits for all the stuff inside the
suspend
function to be done/finished before it returns/resumes. A call to
CoroutineScope.launch
or
CoroutineScope.async
returns immediately. The coroutine, launched by these two function, runs now concurrently with the code that launched it.
t
yes, but that's not really my question. i have some logic that perhaps should run in a different thread, i have some calls to the database and all the code currently is synchronous and blocking
now i want to destrcuture it, move logic to different threads, maybe break it down and make it concurrent and use the io dispatcher to carry out the db queries
i'm unsure how to do it because i have to decide who has the control about starting coroutines and switching the dispatchers.
i could make all the methods called down the path
suspend
and start coroutines as late es possible, or i could start a coroutine as early as possible and just use different contexts
i'm more or less clueless how to tackle this
s
Say you need to put data from the db onto the screen. The user and the UI (screen) is driving the app, basically. Then in your screen create a
CoroutineScope
, e.g.
val uiScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
Using this
uiScope
, you start initiating your asynchronous stuff, like fetching data from the network/db and then putting it onto the screen. E.g.
Copy code
uiScope.launch { 
    val result = dataSource.getData(input)
    val dataToPutOntoScreen = result.toUiResult() // some (simple) transformation
    view.showUI(dataToPutOntoScreen)
}
Your
dataSource : DataSource
could have an implementation that goes to an actual database:
Copy code
class DataSource {
    ...
    suspend fun getData(input: Input): SomeResult = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
        val db = dbService.getDB()
        val cursor = dp.query { .... } // some query 
        if (cursor.moveFirst) { cursor.toSomeResult() } else { NoResult } 
    }
    ...
}
Here you see the UI creates a scope that runs its Coroutines onthe Main-UI thread. And the DataSource runs its query methods on an IO scheduler by calling
withContext
.
☝️ 1
t
thank you!
maybe that boils down to a rule like "do not use
suspend
on service level, and spread suspend everywhere below" 🤔
s
It depends, but I would argue "yes".
t
alright, thanks for your help 🙂
s
And you'd create/build other Coroutines (using
launch
or
async
) when you need to run them in parallel (google for the functions
coroutineScope { ... }
and
supervisorScope { ... }
for more info on that)
t
yeah trying to convert the code into something using coroutines and
async
in a dedicated
coroutineeScope
because it feels very natural, i realized that
async
already is a coroutine builder, but i only thought of
launch
when i asked the question
for me it feels like
launch
is like the entry point into "non traditional code from a java perspective" while
async
is not... propably and odd thining 😉
s
Yes, you can think about that this way. Especially when dealing with UI (or other frameworks on the 'edge' of your app that call back into your code), you'd use
CoroutineScope.launch
to move from the regular function world into the suspend function world. You'd use
launch
since your UI is a pure side-effect, writing stuff the the screen, not expecting any result, really. In the suspend function world, you'd mostly use plain
suspend
functions, an occasional
launch
for doing other side-effects (e.g. logging), some
async
calls for obtaining some results that can be calculated in parallel with the rest of your
suspend
function's code.
t
oh that's good. using launch at the edge, or for sideeffects and async for parallel decomposition. thanks 😄