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

ursus

07/13/2022, 12:31 AM
By default, do I use
default
or
io
dispatcher? Or rather, when would I want to use io over default? I get that its in the name, but what exactly is io work? Networking? Retrofit has coroutines support, so the thread wont be blocked there. Database? Room has it as well Only thing that comes to mind is file writing/reading? Or would it be bad to just use the
io
? I mean it contracts eventually, right? Can I see how many actual threads are in the
io
at some point?
e

ephemient

07/13/2022, 12:40 AM
Dispatchers.Default
is expected to be used for computation - e.g., things which are CPU-bound, so by default uses a thread pool bounded by the number of CPU cores detected
(with the exception that on a single-core system, it still allows 2 threads)
u

ursus

07/13/2022, 12:42 AM
I get all that but what is a computation in a regular android glorified crud app?
or rather, if networking is suspending, database as well, and I dont do any file writing, is everything else (model mapping, control flow, nothing really heavy duty) a “computation”?
e

ephemient

07/13/2022, 12:45 AM
the Android/Java APIs are mostly blocking - not quite sure what you mean by "networking is suspending, database as well"
u

ursus

07/13/2022, 12:45 AM
I can think of json parsing, but that is streamed with moshi + okhttp, so basically a impl. detail of retrofit library
e

ephemient

07/13/2022, 12:45 AM
compression could be something to consider as computation - it's CPU-bound
u

ursus

07/13/2022, 12:46 AM
well, on android I use retrofit, which is basically sugar around okhttp, which has its own threadpool,
which retrofit wraps with regular
suspendCancelableCoroutine
basically
okay when I think about it, im not sure where the json parsing is ran by retrofit, but I presume its the okhttp threadpool
e

ephemient

07/13/2022, 12:47 AM
in that case there isn't much practical difference in which dispatcher you use, since it doesn't block any of the dispatcher's threads
(if all threads in the dispatcher are already busy then I think there could be some difference in their rejected execution fallback, but I'd have to look at the code to check)
u

ursus

07/13/2022, 12:49 AM
yea but even in this setup, lets say I have a Syncer objects, where its calls network and saves to database — even if net and db support coroutines, if I were to have 100 of these Syncers, and initialize them all at once, with io dispatcher, I do spin up 100 threads right?
e

ephemient

07/13/2022, 12:49 AM
no
if they are properly suspending, the thread is free to run other coroutines
u

ursus

07/13/2022, 12:50 AM
no I mean like this
Copy code
Syncer {
	private val scope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>)

	fun sync() {
		scope.launch {
			val whatever = 1 + 1
			val apiData = apiClient.fetchData()
			val data = apiData.toData()
			database.save(data)
		}
	}
}
so apiclient and database have their own stuff and expose suspending api, so “my” thread basically only does the 1+1 and apiData.toData(), right?
e

ephemient

07/13/2022, 12:52 AM
assuming that's the case, yes
u

ursus

07/13/2022, 12:52 AM
and if so, if I have 100 of these, and I call
sync
in a forloop, would I spin up 100 threads to do the 1+1 computation?
e

ephemient

07/13/2022, 12:53 AM
even a single-threaded dispatcher like
Dispatchers.Main
can run 100 parallel coroutines. it doess not require 100 threads
of course only one will be running at a time, but they can interleave at the suspension points
u

ursus

07/13/2022, 12:53 AM
im aware, lets just stick to this example
e

ephemient

07/13/2022, 12:54 AM
IO is limited to 64 threads by default
u

ursus

07/13/2022, 12:54 AM
i know but still, the 1+1 is technically a blocking code, right?
e

ephemient

07/13/2022, 12:54 AM
if you run 100 syncs, you may briefly use all 64 threads getting every coroutine to the first suspension point of fetchData
it will not use 100 threads
u

ursus

07/13/2022, 12:56 AM
okay so maybe not 100 but yes a lot of those, lets say 50, I get that when it arrives at the fetchdata suspension point its free to do something else, presumably the 1+1 in other syncer
but I do spike memory way way too much, for basically nothing, right?
Copy code
for syncer in hunderSyncers {
	syncer.sync()
}
e

ephemient

07/13/2022, 12:58 AM
I don't understand what you mean by that
u

ursus

07/13/2022, 12:58 AM
Copy code
for syncer in hunderSyncers {
	syncer.sync()
}
if the work is initiated like this, then you’d say for some short moment I do spin up 64 threads? Or whatever the number is, its 10s right? Way more that
default
would?
e

ephemient

07/13/2022, 1:00 AM
possibly, depending on how fast they launch
and whether that is more or less than
Default
depends on the system
note that
IO
and
Default
actually share the same underlying thread pool
actually maybe they don't, gotta check…
u

ursus

07/13/2022, 1:02 AM
I know I’m just trying to figure out if I’m needlessly using
io
well lets assume a normal phone, so 8 cores for example, definetly not 100 cores
e

ephemient

07/13/2022, 1:03 AM
looks like they're separate thread pools instead of views into the same pool, but
IO.limitedParallelism
can also create new views that can go beyond `IO`'s original bounds
u

ursus

07/13/2022, 1:04 AM
are you sure? when I print threads in default and io they look the same to me,
Copy code
DefaultDispatcher-worker-X
anyways, this exact scenario, should I go with
default
or
io
?
e

ephemient

07/13/2022, 1:07 AM
I'd go with IO.
u

ursus

07/13/2022, 1:07 AM
why
e

ephemient

07/13/2022, 1:08 AM
you're going to use however many IO threads there are for networking and db anyway
u

ursus

07/13/2022, 1:09 AM
thats true, but isnt less better? less memory?
n

Nick Allen

07/13/2022, 1:43 AM
Not true. The android libraries tend to use their own thread pools so using Dispatchers.IO for Room or Retrofit would be a waste. You’d start a thread just to dispatch to another thread.
e

ephemient

07/13/2022, 1:44 AM
I mean, it's a fixed amount of overhead. it's either that or bottleneck your usage of room/retrofit.
n

Nick Allen

07/13/2022, 2:23 AM
bottleneck your usage of room/retrofit how?
Just to clarify, room and retrofit have built in support kotlin coroutines. You can define the interfaces with suspend methods and then they are safe to call from any coroutine.
u

uli

07/13/2022, 5:10 AM
Hi @Nick Allen you are completely right. Oh Android i have only ever needed the IO dispatcher for disk based Io. And your example would spawn a lot of useless threads, because they would all be busy for a very brief amount. To launch 100 coroutines i would not dispatch at all and just stay on whatever dispatcher I am on. Changing threads is probably more expensive then the actual work
e

ephemient

07/13/2022, 5:17 AM
if you're starting from a non-suspending context, as the example is, you have to choose some dispatcher
but sure, if you change it to a
suspend fun sync()
then you can
launch(Dispatchers.Unconfined)
to avoid switching threads until necessary
j

Joffrey

07/13/2022, 8:31 AM
Going back to the original use case of running this from a non-suspending function, since you just need to enqueue the coroutines, and most of the work is done on the libraries' own thread pool, I guess either dispatcher would do if you use
limitedParallelism()
to limit the thread spike. But honestly I think
Default
without
limitedParallelism
would be simple enough and allow to spawn all those coroutines and run them up to the first suspension point before they switch to the other thread pools.
👍 1
u

ursus

07/13/2022, 11:20 AM
Would your opinion change if the database usage was blocking? Say using Sqldelight, not Room, which doesnt have suspending function wrapper on purpose because of the way transactions are handled
j

Joffrey

07/13/2022, 11:22 AM
Definitely. As soon as you have actual blocking IO (blocks the thread without using the CPU) then it would be a waste to hold threads from the
Default
dispatcher (and could cause starvation)
u

uli

07/13/2022, 11:23 AM
In-process database is a corner case though, as it can be io&cpu heavy. So in best case, the database itself dispatches io to the io dispatcher and query logic to default
2 Views