Handling multitasking on Android is always a challenging and tedious job. Because Android OS is single-threaded, which means operations will run on UI thread by default unless they are explicitly scheduled to run on a background thread. Long-running operations on UI thread tend to freeze your app and show an ANR (Application Not Responding) error. However, with the Kotlin coroutine adoption, it is much easier for developers to write asynchronous code without blocking user interface and create a way to ease the developer jobs.
Coroutines are lighter than threads and services i.e high number of coroutines can run concurrently without blocking UI and running out of memory. It can also be easily paused and resumed whenever it is required. To achieve a high level of concurrency, programmers can explicitly declare the suspension points and avoid overheads.
Coroutine Core Concepts
- Suspending functions
- Coroutine context and Coroutine Scope or Dispatcher
- Coroutine Builders
Suspending functions are at the center of coroutines. A suspending function can be paused and resume operations at a later time. They can execute a long-running operation and hold for it to complete without blocking other operations. The syntax of the suspend function is similar to a regular function except for the addition of the suspend keyword.
It is represented by CoroutineContext class and indexed set that maps from a CoroutineContext.KEY to a CorountineContext.ELEMENT.
The most important elements in the coroutine context are:
- Job: It represents a background operation and its lifecycle. With this object, we detect the background work status or cancel the operation.
- ContinuationInterceptor: It is coroutine context elements that intercept the continuation of a coroutine, Ex: Coroutine Dispatchers.
- CoroutineName: Name specified by the user for the coroutine.
- CoroutineExceptionHandler: Exception handler used to handle exceptions in the current context.
Coroutine Dispatcher ensures that continuation interceptors and coroutine context elements are executed well and in continuation of a coroutine dispatched to the right thread. They can be used to confine a coroutine to a thread pool, a single thread or allow the coroutine to run unconfined.
- Dispatchers.Default: If the coroutine builder has not specified any dispatcher then the default dispatcher is invoked. It has an appropriate choice of running CPU intensive tasks, backed by a shared pool of threads and limited by the number of CPU cores.
- Dispatchers.IO: Blocking IO operations that are not CPU intensive. It is supported by a shared pool of thread configured to support 64 concurrent operations.
- Dispatchers.Unconfined: This dispatcher runs a coroutine on any specific thread. The coroutine is executed in the current thread but resumes in whatever thread was used in the suspending lambda passed as an argument to coroutine builder
- Dispatchers.Main: It makes coroutine run on the main thread.
Coroutine builders are functions that create a new coroutine to run a suspending function. This coroutine builder can be called from normal non-suspending functions. This is the extension function of the CoroutineScope that inherited the CoroutineContext scope invoked. CoroutineScope controlled the entire lifecycle of a coroutine, and most popular coroutine builders are launch and async.
- launch: This builder does fire and forgets, it does not return any result to caller asides and creates coroutine to run in a background thread and returns a reference to it as a Job object. Through that job object, we can cancel or pause, resume the coroutine.
- async: Async coroutine builder is like a launch coroutine builder, but in its structure returns a Deferred<T> instead of a Job. A Deferred<T> is a light-weight non-blocking future that delivers a result later. You will need to get an eventual result for an async builder, then call the suspending function to await. Async coroutine builder used several independent operations on the parallel thread.
This Job and Deferred has three public boolean properties that check the status of a background job.
- isActive: returns true when the job or deferred has currently run.
- isCancelled: returns true if this job or deferred has been canceled.
- isCompleted: returns true when the job or deferred has completed. And also a canceled or failed job is considered complete due to execution finished.
Coroutines are very easy to use for multi-concurrency and async operations & easy to switch synchronous operations through the suspend function without blocking the main thread, although it does require a bit extra care over the exception handling.