Working with Kotlin Coroutines
Coroutines are a really nice feature with the objective of simplifying async tasks, making them easier to implement and understand.
Traditionally in order to handle async operations you would need to use Threads, Tasks, Executors or Callbacks. All these mechanisms work to some extent. Yes, they get the job done but in some cases the code you write can get really messy and hard to read. This is something that can get quickly out of control in projects with a heavy use of callbacks; some nested callbacks here, some more over there and the next thing you know is that you are in the middle of Callback Hell.
Another problem with the aforementioned techniques is that using key language features such as Exceptions is not always possible.
What are Coroutines?
Coroutines are a way to perform asynchronous work in a sequential way, conceptually they are like threads but better; they are more lightweight and offer more control. It’s worth to note that coroutines are much ‘cheaper’ to maintain than threads; many coroutines can be run in a single thread.
More technically, coroutines are blocks of code that can be suspended without blocking the thread, leaving it available to perform any other work. Suspension doesn’t happen at arbitrary parts of the coroutine’s code but at suspension points which is to say functions that are marked with the suspend keyword.
In the example above we see a pattern to use coroutines, we have a coroutine builder that is in charge of creating and launching our coroutine and a set of potentially time consuming functions marked with suspend invoked inside the coroutine.
Functions marked with suspend modifier can only be called from a coroutine or other suspend function, attempting to invoke them from regular code will result in a compilation error.
Builders
As mentioned before, suspending functions cannot be invoked from regular functions, so the library provides a set of functions used to start coroutines from them; coroutines are launched in the context of a given scope (We will talk about scopes and contexts later on) using any of the builders available. One of the simplest to use is launch, but others are available such as async, runBlocking, buildSequence and more. One of the coolest things in coroutines library is its extensibility, only a few constructs have been baked in the language itself, and most of the implementation has been provided as libraries to allow you customize it as much as you need.
IMPORTANT: runBlocking should not be used inside a coroutine as its only purpose it’s to act as a bridge between regular blocking code to libraries written in suspending style, to be used in main functions and tests.
In a more concrete example, to launch a coroutine you would need to call the builder function and pass in the block of code that you want to start as a coroutine:
In here we launch a coroutine from the global scope, meaning that its lifetime will be bound only by the lifetime of the application itself.
Contexts
The coroutine’s builders can take a CoroutineContext as an argument (by default it uses EmptyCoroutineContext), a context is simply a set of rules of how we want the coroutine to be executed, it’s here where you start noticing the flexibility of coroutines, in the context you can customize how the execution of this specific coroutine should be performed; the options go from the thread pool to use up to how the exceptions thrown in the coroutine should be handled:
In here we tell the builder we would like to run the coroutine with the IO dispatcher, using our custom exception handler and binding it to the Job myJob.
Every context uses a CoroutineDispatcher that determines the thread or threads that a coroutine should be run on. The coroutine can be confined to a given thread or use a thread pool. We have several options already implemented in the library; in the example above we use Dispatchers.IO which is optimized to be used for network or file system operations.
Jobs
A job is an instance of some work to be performed in the background that culminates upon its completion. They can be arranged in parent-child hierarchy, where cancellation of the parent job immediately cancels all of its children. In the example above we pass our own instance of Job to the context of the coroutine, it will be used as the parent job for the coroutine. This means that we can use it to have a little bit more control over the execution of the coroutine, such as terminating it using Job.cancel().
Scopes
We mentioned that coroutines are launched using a scope; scopes are the ones that determines the lifetime of the coroutine, so far we have been using the GlobalScope which basically allows the coroutine to run as long as the application itself is running, this is not always desirable:
Suppose we got an activity in which we launch a coroutine in the GlobalScope, if the user leaves the activity before the slowFetch function has returned the coroutine would still be running, this can lead to memory leaking and potential hard to find bugs. A better approach would be to bind the lifetime of the coroutine with activity’s so once the activity is finished any pending work in the coroutine is canceled. We can do this really easily using scopes; make the activity to implement CoroutineScope and bind it to a Job, so the above example is changed to:
Now we don’t need to worry about memory leaking in the coroutine since it will be canceled once the user leaves the activity, nice!
So this is it, I hope that it helps you to understand what Coroutines are and why you should be using them either server side or in your Android Application; in a latter article we will talk about its use in the Android Environment, specially after the recently announced Room support.