Kotlin Coroutines - So that you async in Android

What, Why & How?

Gaurav Goyal
Microsoft Mobile Engineering
8 min readMar 27, 2020

--

Kotlin comes up with coroutines that help us writing asynchronous code in a synchronous manner. Android is a single thread platform. By default, everything runs on Main Thread (UI Thread) so when its time to run non-UI related operations (E.g. Network call, DB operation, File I/O operations or any time taking task), We dedicate these tasks to different threads and on completion if needed, pass back the result to UI Thread.

Android has its own mechanisms to perform a task in another thread such as Async task, Handler, Services, etc. These all are nice and work pretty well when used diligently. These mechanisms include callbacks, post methods, etc. for passing the result among threads but wouldn’t it be nicer if we can get rid of all these callbacks and can write async code the same way we write sync code.

Yes, That’s how easy your code can look like if you use coroutine. We do not need to put a callback, Next line will get executed once the response comes. You would think that calling it from main thread will block it by the time response comes. Well, If you use coroutine, that would not be the case. It won’t block Main thread or any thread as a matter of fact and can still execute code in sync manner. Well, let’s try to figure out the missing part.

Let’s start by comparing coroutine with thread

In the example above, let’s say we create a new thread and once the task is completed, we pass back the result to UI thread. We can do that right? Yes, for sure but there are few problems with this approach. Let me list down some of them.

1. Passing data from one thread to another is a headache. Also, it does not look very clean. Most of the time we end up using callbacks or some sort of notify mechanism.
2. Threads are expensive. Creating and stopping them is expensive, involves creating own stack. Threads are managed by OS. Thread scheduler adds extra overhead to schedule the threads.
3. Threads are blocking. If you are performing a task as simple as delaying the execution for a second (Sleep), Thread would be blocked and can not be used for any other operation.
4. Threads are not lifecycle aware. They do not have any knowledge of Lifecycle components (Activity, Fragment, ViewModel). A thread will be running even if UI component is destroyed which requires us to handle clean up and memory leaks.

Apart from this, how would your code look like with lots of Threads, Async tasks, etc? We might end up with lots of callbacks, lifecycle handling methods, passing data from one place to another which makes the reading difficult. Altogether, we would end up worrying and writing more about handling all these rather than the actual logic.

So let me say it loud “It’s not just another way of Async programming, it’s completely different paradigm of async programming”. Even if you are not convinced, I would recommend you to try it once. What do we know, it might become your go-to man in this Asynchronous world.

Okay, Enough of chanting. Let’s understand the deal.

Coroutines are light and super fast

Let’s not trust what we have to say to each other and let the code do the talking.

I am going to create 10k Threads. I know, You are wondering why you would ever do that in the real world. Well, let’s do this to understand the impact coroutine can create.

Here, each thread is sleeping for 1 ms. Running this function took around 12.6 seconds. Now let’s Create 100k Coroutines (10 times more), and increase the delay to 10 seconds (10000 times higher). Don’t worry about ‘runBlocking’ or ‘launch’ (Coroutine Builders) for now.

Voila! 14 seconds. The delay itself is 10 seconds. This is how lightweight it is. Not to mention, if I try to create 100k threads, It might take forever.

Now we all agree that Coroutines are super fast compared to Threads but how does coroutine do that.

If you look at the creating_10k_Thread() method, you see there is a delay of 1ms. Well, during this delay thread is blocked. In other words, it can not do anything else in that 1ms and will continue after that 1ms. Also, you can create only a certain number of threads depending upon system cores. Let’s say you can create up to 8 threads within your system. Looking at the example, we are running a loop till 10000 counts. 1st 8 times, 8 threads will be created and would run in parallel. On the 9th iteration, another thread can not be created unless there is an available thread. Since we have put sleep for 1ms for a thread, it would be blocked for that much time and would not be able to perform any other operation. Once the delay is over, next thread will be created. To sum up, threads are being blocked for 1ms which is causing the delay, Total blocking time for the method would be 10000/<No of Max Threads> ms. Additionally, Thread scheduler will be managing your threads which adds up extra overhead.

In case of creatingCoroutines() method, we have put a delay of 10 sec. The thing about coroutine is that it does not block, it suspends. So while it is waiting for 10 seconds delay to be completed it can pick up any other task and resume once the delay is over. Also, coroutines are user-managed, OS does not have a say in it. which makes it even faster. To put it in numbers, each thread has its own stack, typically 1MB in size. 64k is the least amount of stack space allowed per thread in the JVM while a simple coroutine in Kotlin occupies only a few dozen bytes of heap memory.

Just to be sure that we understand the difference in suspend and block clearly. I am adding up one more example. If you are clear with the differences, jump to the next section.

In snippet 1, We are calling fun1 and fun2 methods sequentially on the main thread. Execution will have a delay of 1 second during which thread would be blocked and would not be able to perform any other task. Now let’s try to write same code using coroutine.

In snippet 2, It looks like they are running in parallel but that’s not possible since both of them are getting executed by a single thread. Both functions are running concurrently and that’s because delay function does not block the thread, it suspends the thread so now without wasting time same thread can start performing next task and can return to it once the other suspended function (delay) returns to it.

A coroutine can provide a very high level of concurrency with very small overhead. Multiple threads can also provide parallelism but there is blocking and context switching. Coroutine suspends the thread and does not block it so that it can switch to another work. With a lot of coroutines doing very small bits of work and voluntarily switching between each other, It can provide efficiency and faster execution which can never be achieved from a scheduler which is why you can have thousands of coroutines working together as opposed to tens of threads. Let’s move to the next question.

How does coroutine suspend itself?

If you look at the output, you would find that ‘completionHandler’ is executed after ‘asyncOperation’ is completed. ‘asyncOperation’ is running in a background thread and ‘completionHandler’ waiting for it to be completed. In ‘completionHandler’, a textview is being updated. Let’s look at the byte code of ‘asyncOperation’ method.

Check the 2nd line from the top, there is a new parameter called ‘continuation’ added to asyncOperation method. Continuation is the real deal, this takes care of code suspension. Continuation gets added as a param to the function if it has ‘suspend’ modifier to it. Continuation stores the current state of the program. You can think of it like passing rest of the code (in this case completionHandler() method) inside Continuation wrapper. Once the current task is completed, continuation block will be executed. So every time you are creating a suspend function, you are adding continuation param in it which is wrapping rest of the code from same coroutine.

If you are here, I hope you have found a reason to use coroutine. What we have seen is a small part of, what coroutine has to offer. Additionally, Coroutine works really well with Livedata, Room, Retrofit, etc. So I will encourage you to start using coroutine in your projects. Only if you try it, you would know the power of it. I will pause here and leave you with one example.

In the example, I have created a BaseActivity class and a MainActivity class which is inheriting BaseActivity. In MainActivity.asyncWait, We are performing 2 Async operations and logging the result once both are completed. (Its not complete project, this contains only code related to coroutine)

I recommend you to read through the example thoroughly and try to understand the terminologies on your own. You might want to start with Dispatchers and Coroutine builders.

Lastly, thank you for reading the article! Any questions and suggestions are most welcome. See you soon.

--

--