One Stop Shop for Kotlin Coroutines

Kotlin Coroutines

If you’re already familiar with the basic workings of Kotlin and now you feel the time has come to extend your knowledge a bit further with coroutines (but it looks a little overwhelming), you’ve come to the right place. In this post I will cover the basic concepts and inner workings of coroutines in a plain and simple manner.
Before I jump into it, let’s have a high level overview of how threads work in general, what concurrency is and how coroutines come into the picture of all that.

kotlin coroutines and everything about them

Threads, concurrency, coroutines

We could say that threads are organized units of computations which can be executed by the CPU. In general the CPU’s thread scheduler allocates a certain amount of time for each of these threads to run. Then, after a certain period of time, they are switched to another thread to start work. It simulates as if they worked simultaneously. But how many threads can run in a truly parallel manner? Well, it’s defined and limited by the number of cores in the CPU.  A single-core CPU can only process one thread at a time but a dual-core CPU can work with two threads at the very same time and so on… That’s how true parallel work can be achieved with threads.

Concurrency on the other hand is the concept aiming at proper structuring and managing of the various computations in a way that they can be executed independently from each other as efficiently as possible. So a task could be chopped into several parts then they could get executed on different CPU cores, in different threads. And this is where coroutines come into play.

Conceptually coroutines are somewhat similar to threads in regards of how they manage units of work but they are running ON threads. So why use them instead of threads? Well, creating more threads to achieve a higher level of concurrency can become very costly at the end due to their memory allocation requirement. Creation of coroutines on the other hand is much cheaper, you can create tens of thousands of them without any problem. Coroutines are not bound to any specific thread, execution can start on one thread then continued on another. They are non-blocking, they can suspend at suspension points (which are managed on code level) to give up execution time and free up the thread’s resources for other coroutines that have been dispatched to the same thread. So what does all that mean?
Let’s say we start some work in coroutine A, which is executing on thread X. It’s running on its own then at a certain point we would need the result of some heavy calculation or some network request to continue. This other task is wrapped in another coroutine, coroutine B and it is dispatched to another thread, Y. So what happens is, execution of coroutine B will start on thread Y but instead of coroutine A waiting for B to finish, it suspends its work and says hey, someone can take my place on thread X while I wait for coroutine B. When the result from B is ready, A will continue execution from the same context.

This is just one simple example but you get the idea hopefully 🙂 Let’s move on now to how actual creation of Kotlin’s coroutines with the various building blocks can happen.

Declaring coroutines

The top of the coroutine structure hierarchy is occupied by CoroutineScopes. Every coroutine has to live in a CoroutineScope, there is no way around. CoroutineScopes ensure that the lifespan of coroutines which belong under the same umbrella in our application can be managed properly so for example they don’t outlive the entity which they are related to. 
CoroutineScopes are defined with their CoroutineContext variable which can be constructed from a couple of types of elements and give us the execution context for the scope. Once you have the scope with the context, you can start to declare coroutines with coroutine builders and nesting suspending functions (I will get to these later in the post, for now just go with the image of regular functions which can suspend execution in the way of the example in the intro). 
Now, when I mention that coroutines have to live in scopes what I mean is that builders are extensions of the CoroutineScope interface so trying to declare them outside of the scope on the regular randomly will result in your IDE starting to cry. And same goes for suspending functions when you try to access them from regular code.

CoroutineScope, CoroutineContext

There are two ways you can declare a CoroutineScope, either by extending a class (so the whole class becomes a scope) and override the scope’s coroutineContext variable:

Or you can declare it as a variable by instantiating the CoroutineScope interface and passing the context elements as arguments then use this variable to extend coroutine builders from:

If possible, I prefer the first way over the second, that way you can save repetition of using the scope variable whenever you have to use a new builder.
Note: instantiation with EmptyCoroutineContext is not good practice, I did it only because we’ve yet to go through the types of context elements but let’s take care of that now.

There are four types of elements that can be used to construct the CoroutineContext: Job, ContinuationInterceptor, CoroutineExceptionHandler, CoroutineName.
Let’s take a closer look at each:

  • Job:
    Jobs are basically entities for modelling the lifecycle of coroutines and to represent a hierarchical structure between them. They can be arranged in a parent-child relation (with the usage of the Job’s (‘parent’) parameter) with the following rules between them:
    • Cancelling or failing of the parent will cancel the children as well.
    • Failing of a child will cause the parent to cancel as well (thus all other children of that parent will be cancelled).
    • In order for children to be able to fail on their own without cancelling the parent and the other children below the parent, you can use a SupervisorJob instead of a Job for a top level job like the scope’s job.

Continuation Interceptor:
This element is responsible for managing the thread pool the coroutine is running on as well as handling the suspension and resuming that happens during execution. These interceptors in Kotlin’s coroutines are implementations of CoroutineDispatcher class. The out of the box dispatchers are:

  • Dispatchers.Default:
    If the coroutine context is empty, this will be the default dispatcher to handle coroutines. It uses a shared pool of threads with thread count between 2 and the core count of the cpu.

  • Dispatchers.Main: 
    On android this is the mainThread dispatcher. You have to import kotlinx-coroutines-android module to use it.

  • Dispatchers.IO: 
    Shares threads with Default dispatcher, so by switching from Default to IO may likely result in working on the same thread. You may want to define your own threadpool for IO tasks.

  • Custom threadpools:
    You can define them with the Executor class’ threadpool creator functions which will return with ExecutorService type. You can then cast those as a coroutine dispatcher with the .asCoroutineDispatcher() extension like this for example:

CoroutineName:
Adding it to the mix comes in handy when it comes to debugging.

CoroutineExceptionHandler:
Having an exception handler in the context of coroutines is somewhat a must if you don’t want your app to crash when some coroutine throws an exception for some reason. Since exceptions are propagated upwards in the coroutine hierarchy, it is good practice to have an exception handler in the scope’s context to surely catch any thrown errors.
Be careful! If a child coroutine has exception handler in its context but its parent doesn’t have one then when the parent rethrows the exception, the app will crash. Also worth noting: when multiple errors being catched by the handler, only the first one will be handled, others will get swallowed up. 

Concatenating the context elements is this easy, thanks the plus operator function in the CoroutineContext interface:

Coroutine builders

Now that the basics of scope and context have been covered we can finally start looking at builders and how they work and behave. 
In this section I’ll go through the torch-bearers, launch and async (+ runBlocking) but later in the post we’ll get to some more other really worthy builders.

  • launch:
    The way of launch is that it creates a coroutine with the code inside its block and starts execution right away. It doesn’t block, returns immediately and the rest of the code outside of the launch block will continue its execution:
  • The point of this example is really just to show the nature of non-blocking behavior of launch so even though the first launch had a delay in it, it didn’t hold up execution of the rest of the code. Easy peasy. 
    output:
    Hello from second launch
    Hello from first launch

    Launch doesn’t return any result but it still has a return type which is a Job. So if you declare a launch block with a variable, you can control the lifecycle of it. More on this later but first let’s check async and the somewhat odd one out builder, runBlocking.

  • async:
    Like in the case of launch, execution of async starts instantly and it returns with Deferred<T> (Kotlin coroutines’ future type) which has a result. Deferred is also a Job by the way so you can manage it the same way as launching coroutines. In order to access the result of Deferred, you have to call await() which will suspend the coroutine until the work has been finished. Let’s see an example, then I’ll explain further:
  • First of all I’ve assigned the async builder to a variable. Otherwise I cannot reference the coroutine and cannot do anything with the result. When execution reaches to the println line the async block has not finished it’s work yet (due to the 1 sec delay) so when await() is called, the outer coroutine (launch) will suspend execution until someAsyncTask has finished its work. No surprises, after the one second delay async returns with the result and the message gets printed at the end of the block and launch will complete as well.

    Question you might ask: why did I put all this in a launch block? Because the await() function, as mentioned before, is a suspending function thus it can be called only from another coroutine or another suspending function (and those can be called from some coroutine as well at the end of the day). It makes sense if you think about it. What is the thing that would suspend in a regular code without coroutines if the code would just hit the await()? The scope itself is just a scope, not a coroutine, it cannot suspend 🙂

  • runBlocking:
    Unlike launch and async, as its name suggests, it will block the caller thread so use this builder only in places where you have to bridge your blocking code with the non-blocking coroutines code, preferably with switching context immediately to a different Dispatcher. Be careful though! Even when switching Dispatcher runBlocking will block outer code execution until all code inside of it is finished:
  • So this builder is usually not very useful in production code but rather in testing situations.
    output:
    counting my fingers 1
    counting my fingers 2
    counting my fingers 3
    counting my fingers 4
    counting my fingers 5
    geez, 5 seconds wasted

Before moving on I’d like to point out some of the workings of builders:

First, builders automatically inherit and will use the context of the scope by default but through the structuring process of coroutines, different context elements will be required to handle each situation accordingly. You can handle these situations and make changes to the scope’s context with passing new context element(s) to builders’ context parameter. When you do that what’s happening behind the scenes is that the new element will be added in a ‘context + newContext’ way where the ‘context’ is the parent context and the newContext is the context passed as argument to the builder. If the type of element you add has not been present in the parent context to begin with, then from this point downwards the parent-child coroutine hierarchy will be used in the context. And if the type of element has already been present, then it will be switched to the new element. 

output:
parent
inner launch

And second, launch and async both start their execution immediately however they both have a ‘start’ parameter which can be useful when you would want to tie the start of the execution to some event for example:

More on Jobs

What options do we have after execution has started? How can we cancel jobs? How can we make sure a particular launch finished before starting a new one? When do jobs complete?
Fortunately these things were made pretty straight forward by the engineers at Jetbrains.
As for cancellation, you just simply have to call cancel() function on the job. To cancel a scopes job is as simple as the following: 

To cancel just the coroutines inside a scope or children jobs of a parent job without cancelling the parent job itself you could simply call the cancelChildren() function on the parent job.

Cancelling a coroutine will immediately transition it from the active to the cancelled state but it will be considered complete only when all code inside of the coroutine has finished execution and all children coroutines it might contain have also been completed. To check the state of jobs in code is as easy as checking the the job instance’s isActive, isCancelled, isCompleted boolean values.

Last thing is if you have to make sure a launch is completed before going forward with execution of the coroutine; use the join() function. This is the sibling of Deferred type’s await() function by the way.

The same code with cancelAndJoin() will cancel the job and then the continue execution with the rest of the coroutine.
And if you could group together several jobs by putting them in a list, you could use the joinAll() function to wait for all of them to complete before moving on.

Suspending functions

The term suspending functions and suspension have been brought up a few times in the post but I haven’t really gone into how these functions’ work. If we look under the hood, these functions are just like regular functions with an addition of an implementation of a Continuation<T> interface. This interface includes the context which is the CoroutineContext of course and a resume function which can return some type of value or with an exception.

Suspending functions can be called from coroutines only and at some point in the function there has to be a suspension point (in this case it is the await() function) where the function will suspend.

With that said, don’t be surprised if you declare a function with a suspend modifier that does not suspend at any point. It will be treated as a regular function with a coroutine and the suspend keyword will become useless and can be deleted as launch by itself is not a suspending coroutine:

Other coroutine goodies

At this point, the essential basics of coroutines have been covered but there is some other stuff worth to mentioning that can be used to further tweak coroutines.

  • coroutineScope and supervisorScope
    When you have a more complex suspending function, but even with basic ones I would suggest considering the usage either of these (depending on desired behavior of course) builders due to the fact that, as mentioned before, exceptions are propagated higher up the coroutine hierarchy and you might want to catch and keep these exceptions at bay and not cancelling whole other parts of the coroutine hierarchy.

    Both functions inherit the outer scope’s context and the mechanics of the isolation is due to the fact that they both override the context’s job with a new one, coroutineScope with plain Job(), supervisorScope with SupervisorJob().

  • suspendCoroutine<T>()This function suspends the caller coroutine until the block returns with the usage of the continuation’s resume function. Resuming can happen with a value or exception as well.
    (actually, I found suspendCancellableCoroutine to be better because it provides an invokeOnCancellation method to run upon cancelling the outer coroutine)

withContext<T>()

This builder is used to create a coroutine with the context passed as an argument, suspends until its block completed execution, then returns with a result. 

Combining it with coroutineScope:

suspendCoroutine<T>()

This function suspends the caller coroutine until the block returns with the usage of the continuation’s resume function.
(actually I found suspendCancellableCoroutine to be better because it provides an invokeOnCancellation method to run upon cancelling the outer coroutine)

withTimeOut<T>()

A way to limit computation time for suspending blocks can happen with usage of either withTimeOut<T>() or withTimeOutOrNull<T>().
Both are pretty self explanatory. Their parameter is the time limit of execution in milliseconds. If execution has not been finished within the given time frame, the coroutine will throw an exception or in case of withTimeOutOrNull<T>() it will return null.

This example function will happily return:



  • delay()I think it doesn’t require much explanation, speaks for itself as well, you’ve already seen this one being abused in the post a dozen times for the purpose of imitating some computation time.  The function’s parameter type is Long and it will delay the coroutine with the value passed in milliseconds. As opposed to Thread.sleep() it is non-blocking of course.

Channels

(some parts are still in experimental!)

Channels are conceptually similar to a pipeline. You can send and receive different types of elements to and from them in a first in first out manner. They have different types of buffer capacities: Rendezvous, Conflated, Unlimited and you can define a custom capacity as well: 

  • RENDEZVOUS 
    A rendezvous channel means that the channel does not have a buffer it processes one element at a time.
    Before sending an element to the channel only its sending function is active and the receiving function is suspended. Once the element is sent to the channel the sending function suspends and the channel’s receive coroutine is activated for retrieving the element. Once the element is retrieved receiving is suspended again until another item is sent to the channel.

  • CONFLATED
    With a Conflated channel you can send elements to the channel but only the most recently sent element can be received from it, others will be lost.

  • UNLIMITED
    An unlimited buffer means the channel’s capacity is virtually limited only by memory, all elements can be received in a first in first out manner.

These capacities represent Int values, 0 for Rendezvous, -1 for Conflated, Int.MAX_VALUE for Unlimited.
Besides these, you can specify a custom Int as well and then it will become the buffered capacity of channel.

The types of channels in coroutines are SendChannel<T> and ReceiveChannel<T> and there is also BroadcastChannel<T>. 
A SendChannel is the type of channel you can send elements to while ReceiveChannel is the channel you receive elements from and BroadcastChannel is a special kind of SendChannel, you can subscribe to its elements and receive items from it from multiple places from your code.


You might ask how can these channels be useful if on their own they do just one part of the work? 
Well, to get around these, you could instantiate a Channel<T> variable which implements both SendChannel and ReceiveChannel interfaces for example and that could be one solution. But coroutines have special builders for the various types of channels. These builders, of course, have the capacity parameter and also the context parameter just like other coroutine builders:

  • produceThere’s the produce builder which will return a ReceiveChannel so you can receive elements from the result but the interesting part is that the receiver of the produce block is a ProducerScope which implements the SendChannel interface so inside the produce block you can use the SendChannel’s send function:

And then somewhere else in the code you can retrieve the elements from the variable:

actor

The other builder is the actor. And the way of actor is somewhat similar to produce but the roles are the opposite. Actor will return a SendChannel but the block’s receiver is an ActorScope which implements the ReceiveChannel interface. So, instead of sending the values in the block and extracting them elsewhere, you can define what kind of work you would want to do with the elements which will be sent into the channel.

Then using the actor from somewhere else in the code:

  • And this will update the someTextView’s text each second with numbers from one to ten.

    Again, this is not something you would probably use channels for but imagine for example if you’ve defined a sealed class with different types of classes inside and the actor’s type would be this sealed class you could then use a ‘when’ block to route the work to different worker parts of the code. 

    Actors are also a great way to deal with shared mutable states aka variables that can be accessed from different parts of the code because coroutines are executed sequentially, one after an other and inside the actor, the shared state is updated through the channel’s messages which also happens sequentially.

  • broadcastIn the case of ReceiveChannel, if an element has been received from it then it is removed from the channel so there’s no way to retrieve the same element twice. That is what BroadcastChannel is for but I won’t discuss it here as the very function used to subscribe to the elements and receive it from multiple sources (openSubscription()) is still in experimental and might be changed in the future but it’s worth keep an eye on it and see how it develops.

closing channels

In the case of produce, once its block reached its end the SendChannel will be closed for sending so it’s taking care of itself automatically. But in the case of actor, you have to close the channel manually once it’s not needed anymore with the close() function. 

offer(), poll()

Also worth to mention some subtleties for ways of sending and receiving from channels which could happen also with offer() and poll() functions. 
In the case of a SendChannel you can use the offer to try sending elements to the channel which will succeed only if the capacity of the channel allows sending more elements. It returns a Boolean as an indicator for that.
With ReceiveChannel poll will try to retrieve an element if it can or returns null if the channel is empty.

Select

(some parts are still in experimental!)

A select clause is also suspending function and it does great work when it comes to select coroutines which meet some kind of criteria first or fails when it cannot happen for some reason. 

In case of async tasks it selects whichever deferred is resolved first:

In case of ReceiveChannels it will select whichever channel received an element then executes the block with the value:

In case of SendChannels select will choose the channel where the given parameter can be sent first. 
The parameter’s type has to match with the type of the channels you send it in (so in this case both channels have to be of type String):

For simplicity I used Unit as a return type in each case but select can return with other types as well, depending on what type of value will be returned from the blocks.

Closing thoughts

Coroutines in real code with real world use cases can get a bit more complex of course. But with some looking under the hood investigation of the various components and with a couple of hours of playtime, you can get your head around each topic pretty easily and from that point it will look much simpler and more understandable than it seems at first sight. Don’t be shy to check on others’ solutions as well, you can always learn a lot from them and since coroutines left its crib with stable versions late October ’17, I think we can expect more and more solutions built with this technology will surface in the future. 
Also don’t forget to give yourself some pats on the back for getting through this post and if you feel like sharing a thought or asking something please hit the comments section 🙂

Editor’s note: Wanari is a custom software development company, established in 2000. We are committed to technological excellence and building functional, robust and quality software. Check out our website to learn more about what we do.

Csaba Szenczi

Csaba Szenczi

Android developer

Latest posts by Csaba Szenczi (see all)