Introduction to Coroutines
This is the first of a series on using coroutines for Android. This first part - Introduction to coroutines, aims at introducing coroutines, it's meaning and terminologies that are useful in the world of coroutines in Kotlin for Android Developers.
What are Coroutines?
Coroutines are essentially lightweight threads that are launched by a coroutine builder in a context of some coroutine scope. Similar to threads, they take a block of code and have a lifecycle - it can be created, started, suspended, resumed and terminated (cancelled). Coroutines are non-blocking and can have many of them running on a single thread. In a world of a coroutine, suspending and resuming work replaces the need for callbacks. The ability to suspend and resume makes them an instance of a suspendable computation.
Creating coroutines
Coroutines are created using coroutine builders functions. These builders are functions that take some suspending lambda as an argument, creates a coroutine, and, optionally, gives access to its result in some form. Examples of builders are launch
, async
, runBlocking
and produce
(Experimental). All builders are extension functions on CoroutineScope
launch
- Launches a coroutine mainly for its side effect, has no result but returns a jobasync
- Returns a single value with the future result. The type of the returned value isDeferred
, a special kind of Job with a result.produce
Produces a stream of elements. The result returned from this is a ReceiverChannel.runBlocking
Creates a coroutine which blocks the thread while it runs.
Coroutine builder can take an optional coroutine context parameter like job, coroutine exception handler and dispatcher.
First coroutine blocked main thread with Thread.sleep
In the snippet given above, we create our first coroutine in a Global Coroutine scope and launched it using a launch
coroutine builder. The delay function in coroutines is similar to Thread.sleep()
in threads. It performs a pause in the execution of our code. However, the underlying difference is that delay is a suspending function which suspends its current scope in our case the coroutine scope of the launch
builder without blocking the main thread, while Thread.sleep()
blocks the thread. Without blocking the thread our snippet will print hello and the main thread completes before printing "World".
We can keep our main function alive by replacing Thread.sleep()
with a runBlocking coroutine builder, the runBlocking
builder can block the thread from closing until it terminates.
Use Cases for Coroutines
- Asynchronous computations
- Background processes occasionally requiring user interaction, e.g. update views on completing background tasks
- Channel-based concurrency
In Android, coroutines are used mainly to solve two frequently faced problems, namely
- Long-running tasks that could block the main thread.
- Disk operations from the main thread.
Terminologies
Now that we have been exposed to coroutines, the next step is to look into some concepts that will help us to understand coroutines better.
Suspending function
A Function that is marked with suspend modifier, it may suspend execution of the code without blocking the current thread of execution by invoking other suspending functions. A suspending function cannot be invoked from a regular code, but only from other suspending functions and from suspending lambdas.
Suspending lambda
This is the block of code that runs in a coroutine. It is the Short syntactic form for an anonymous suspending function. Looks exactly like an ordinary lambda expression but with a functional type marked with the suspend modifier
Suspending function type
Function type for suspending functions and lambdas
It is just like a regular function type, but with suspend modifier.
Suspendable point
A point during coroutine execution where the execution of the coroutine may be suspended. All points in coroutines where suspendable functions are invoked are suspendable points.
Continuation
Is the state of the suspended coroutine at the suspension point. When a suspendable function is invoked in a coroutine, it suspends the coroutine. Continuation represents the rest of its execution after the suspension point.
Dispatchers
A Dispatchers can be thought of as the thing running the coroutine. They are to Coroutines what the thread pool is to Threads. There are four dispatchers in coroutines namely main, IO, default and unconfined. In Android, the main dispatcher is the android main thread, the default dispatcher for CPU bound tasks which is fixed to the number of cores on the device while the IO is fixed to 64 threads.
Job
A cancellable thing which represents a background job. It is used when we care more about the side effects and execution process than the return value of a background process.
It has a life-cycle which starts from either new or active state and ends in its completion state. Instances of a Job can either be created with launch coroutine builder or with a Job()
factory function. Jobs can be used to define the parent-child relationship of coroutines. Passing a job as a coroutine context parameter to a builder will break the parent-child relationship.
CoroutineScope
An interface which contains a single property only — coroutineContext
It defines the scope for new coroutines
CoroutineContext
Is an indexed set of Element instances. An indexed set is a mix between a set and a map where every element in this set has a unique Key. The CoroutineContext itself is immutable. Its elements are the Job of the coroutine, continuationInterceptor(mainly dispatchers) and coroutineExceptionHandler. New CoroutineContext can be created from the addition of new element using the plus operator which produces a new set containing the newly added one.
This first part introduces some of the core concepts of coroutines. Subsequent posts in this series will be on the following
- Coroutine Builders, Exception handling and propagation.
- Data streams in coroutine - Channel and Flow.
- Application of coroutine in Android - Room, Livedata, ViewModel, Lifecycle, WorkManager etc.