Dispatcher.IO looks like better option for default dispatcher

After reading some RxJava articles i mentioned that all authors recommend use Schedule.computation only for hard CPU jobs, while Scheduler.IO looks as better option for everything else (IO operations itself, callbacks execution, data mutation etc) because execution doesn’t stop if all continuation threads are busy.

So my question is, why in coroutines default dispatcher is Dispatcher.Default? As I understand it has the same behavior as Scheduler.computation and it means same problems. Maybe I’m missing something. Can you explain please?

I already have an answer here: https://github.com/Kotlin/kotlinx.coroutines/issues/2410 but it’s still not clear for me.

So if Dispatcher.Deafult should be used only for small computations why library doesn’t contain dispatcher for long computations? Otherwise it’s easy to block default execution. I will run 8 launch with some loop computation and the 9th launch will block. But if I’ll launch 9th coroutine on Dispatcher.IO it will work.

I understand that for common tasks using Dispatcher.Deafult is better option for system resources optimization, but if I will run my code on 2 cores device it can bring some problems.

So that should I do if my Android application pretty complicated and has some parts with long computation blocks?

Yes, but the 65th not.
Dispatcher.IO isn’t unlimited.

You should define a proper Dispatcher, you can starting form an Executor.
You may want to create an unlimited Dispatcher for many CPU long tasks, but please consider that a fork bomb never works properly :wink:

The reason Dispatchers.Default is a good default dispatcher is because it is limited to the number of physical threads on your machine (unless it only has one core, but that’s another story). Aligning the number of logical and physical cores like that means you reduce the chance of context switches during your application lifetime, something that is extremely (relatively speaking) expensive.

This comes with a caveat though, as you mentioned. If you have 8 phyiscal threads on your machine and you launch 8 long-running tasks then the 9th task will have to wait until one of those tasks reaches a suspension point.

The ‘solution’ to this is to not have long-running tasks run on the default dispatcher. If you do have a computationally intensive piece of code running you can introduce suspension points by placing some yield() calls in the code, which might suspend the current operation and yield the thread to other tasks.

Then what about IO tasks? IO has mostly been implemented as an ‘atomic’ operation; you do a request to a URL, wait for a response and block the thread. You can’t really put your own yield calls anywhere and you’ll be waiting for a (relatively) long time to get an answer from the request.
That’s what Dispatchers.IO is for, it allows you to run a bunch of blocking IO without sabotaging the default dispatcher. This pool is generally limited to 64 threads, which means that you do have the increased potential of context switches, but given the cost of blocking IO this extra overhead can be rationalized as acceptable.

If you run into a CPU intensive task that you cannot yield in (because you don’t control the source code of the function for example), then you are in a bit of pickle. I don’t really understand enough about coroutines to know if there even is a solution there, but it doesn’t seem like you have this problem.

1 Like

Thanks for the good clarification of the Dispatchers approach!

But at the same time i want to understand the best approach for CPU intensive tasks that i cannot control. On the one hand i would like to use fixed pool to avoid additional thread creation and context switching and with the other i want to ensure general code execution without blocking. And i’m Android developer so i have to think about 2 CPU devices. Looks like i have to check in runtime before general computation - are there any free Default threads (or threads that don’t busy by long operations??) and if not, create another one, while before intensive CPU computation i have to use only Default threads without addition creation.

Can you suggest some approach for me? Maybe custom Dispatcher with additional fixed 1-4 threads pool (depends on current device CPU) which will be used only for uncontrolled computation tasks?

Use the already configured dispatchers and make test test test test.
If it works as expected, then there isn’t an issue.
Without test you cannot catch an issue and you cannot fix an nonexistent issue.

1 Like

Reasonable advice) ok, thanks