How "light-weight" are Coroutines really for real-world Android development?

Viewed 330

When you start to learn about Kotlin Coroutines, one of the first examples you see is that it is possible to start hundreds of thousands of Coroutines, but run into OutOfMemory Exceptions when trying the same thing with Threads.

While this example is "cool", I don't think it is useful and even a bit misleading for real-world Android application development. Sure, you can start hundreds of thousands of Coroutines that do nothing but delay(), but in reality, you either start coroutines that do some kind of IO or some kind of calculations.

If you, for instance, start hundreds of thousands of Coroutines and each performs a network request with Retrofit, it wouldn't be any more lightweight than using Retrofit conventionally without Coroutines, because okhttp still blocks a thread for each request.

The same is true when you are doing calculations in coroutines. Then it is also not more lightweight, as the threads which are doing the calculations are blocked until they have finished.

So is my assumption correct, that without the existence of non-blocking IO libraries, my app will not get more efficient or "light-weight" just by using Coroutines or am I missing something?

2 Answers

Sure, you can start hundreds of thousands of Coroutines that do nothing but delay(), but in reality, you either start coroutines that do some kind of IO or some kind of calculations.

Not necessarily. Coroutines can be useful for reorganizing other types of code as well, such as working with views.

If you, for instance, start hundreds of thousands of Coroutines and each performs a network request with Retrofit, it wouldn't be any more lightweight than using Retrofit conventionally without Coroutines, because okhttp still blocks a thread for each request.

My guess is that you are trying to say that OkHttp uses a thread pool. That is definitely the case.

The same is true when you are doing calculations in coroutines. Then it is also not more lightweight, as the threads which are doing the calculations are blocked until they have finished.

This is the OutOfMemoryError scenario from your original paragraph, if you are creating individual threads per calculation.

Coroutine dispatchers are based on thread pools. So are RxJava schedulers. So is AsyncTask, for that matter. The "hundreds of thousands of Coroutines" scenario compares not using thread pools to using thread pools, in the end. So, yes, the "hundreds of thousands of Coroutines" comparison is not remarkable, because there are other ways of using thread pools.

You're partially correct, opening multiple blocking IO request will queued into Dispatchers.IO, and they are going to be executed sequentially if max number of request has exceed 64 (max number of threads on IO Dispatcher) i.e. one by one as the threads are going to be freed up.

Coroutines aren't magic, they simply converts callbacks into a sequential looking code via a Continuation which behind the scenes works as a callback handler.

Coroutines in real-world application usually get benefit from ease of changing threads, cold flow, channels whereby suspended coroutine receiving values does not essentially occupy thread till it is resumed.

While RxJava does the same, coroutines are benchmarked to consume less memory and takes significantly less time to complete check benchmarks here.

You could also use coroutines to actually make deep recursion possible by using the heap memory instead of stack, see Roman's elizarov implementation of deep recursion using the coroutines aren't they lightweight enough?

Related