Introduction
In today’s world of API calls and database access, concurrency and asynchrony play a central role for developers. On Android, apps may not actually perform many computations but rather spend most of their time waiting—whether it be for weather data fetched from a server, user data retrieved from a database, or an email to arrive.
There are several terms that are often confused with each other: concurrency, parallelism, multitasking, multithreading, and asynchrony.
First, concurrency itself only means that different parts of a program can run out of order without it affecting the end result. This does not imply parallel execution; you may have a concurrent program running on a singlecore machine with tasks that overlap in time because processor time is shared between processes (and threads). However, concurrency by this definition is a necessary condition for a correct parallel program - a nonconcurrent program run in parallel produces unpredictable results.
The terms parallelism and multitasking are used synonymously and refer to the actual parallel execution of multiple computations at the same time. In contrast to concurrency, this requires a multicore machine. The fact that parallel execution is also called concurrent execution may be the source of some confusion.
Multithreading is one particular way to achieve multitasking (parallelism), namely via threads. A thread is intuitively a sequence of instructions managed by a scheduler. Multiple threads can live inside the same process and share its address space and memory. Because these threads can execute independently on multiple processors or cores, they enable parallelism.
Lastly, asynchrony allows operations outside of the main program flow. Intuitively, this is usually a background task that is issued and executed without forcing the caller to wait for its completion - such as fetching resources from a network. This is in contrast to synchronous behavior, meaning that the caller waits for the completion of the previous operation before performing the next operation. This may make sense if the synchronous task is actually performing computations on the CPU but not if it is waiting for a third party, such as a REST API, to answer. An important aspect of asynchrony is that you don’t want to block the main thread while waiting for a result - in fact, the terms asynchronous and nonblocking are used synonymously.
In this tutorial, you will discover coroutines as a more lightweight solution that also allows writing intuitive code. With Kotlin’s coroutines, you are able to write sequentialstyle code without any syntactic overhead.
There are two types of coroutines:
Kotlin implements stackless coroutines — it’s mean that the coroutines don’t have own stack, so they don’t map on the native thread.
Conceptually, you can imagine coroutines as very lightweight threads. However, they are not threads; in fact, millions of coroutines may run in a single thread. Also, a coroutine is not bound to one specific thread. For example, a coroutine may start on the main thread, suspend, and resume on a background thread later. You can even define how coroutines are dispatched, which is useful to distribute operations between the UI thread and background threads on Android and other UI frameworks.
Coroutines are similar to threads in that they have a similar lifecycle: they get created, get started, and can get suspended and resumed. Like threads, you can use coroutines to implement parallelism, you can join coroutines to wait for their completion, and you can cancel coroutines explicitly. However, compared to threads, coroutines take up much fewer resources so there is no need to keep close track of them as you do with threads; you can usually create a new coroutine for every asynchronous call without worrying about overhead. Another difference is that threads use preemptive multitasking, meaning that processor time is shared between threads by a scheduler. You can imagine threads as being selfish; they do not think about other threads and use all the processor time they can get for themselves. Coroutines, on the other hand, use cooperative multitasking in which each coroutine itself yields control to other coroutines. There is no separate mediator involved to support fairness.
Before diving deeper into coroutines and exploring code examples, you should set up coroutines in a project to follow along: Add the core dependency to the dependencies section (not the one inside of buildscript) of your app’s build.gradle file:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1"
You can replace the version number with the latest one.
With this, you should now be able to use coroutines in your project.
Suspending functions
Suspending functions are functions with an additional superpower: they may suspend their execution in a nonblocking way at welldefined points. This means that the coroutine that is currently running the suspending function is detached from the thread on which it was operating, and then waits until it is resumed (potentially on another thread). In the meantime, the thread is free to perform other tasks. Following listing defines fetchUser
as a suspending function.
suspend fun fetchUser(): User { ... }
The suspend
modifier transforms a normal function into a suspending function. In principle, nothing more is required. As you can see, the function signature itself is completely natural, without additional parameters or a future wrapping the return value.
At this point, it’s important to understand that the suspend
modifier is redundant unless the function actually contains a suspension point. Suspension points are the mentioned "welldefined points" at which a suspending function may suspend. Without a suspension point, the function is still effectively a blocking function because there is no way for it to suspend execution.
suspend fun updateWeather() { val user = fetchUser(userId) // Suspension point #1 val location = fetchLocation(user) // Suspension point #2 val weatherData = fetchWeather(location) // Suspension point #3 updateUi(weatherData) }
The updateWeather
function may yield control in a nonblocking way at each of the suspension points - and only at these points. This allows waiting until the result of each call is available without blocking the CPU. The three calls are executed sequentially by default so that, for instance, line three is only executed once the second line completes.
Excited about suspending functions, you may try to call a suspending function from within onCreate
in your Android app but find that this is not possible. If you think about it, this makes sense: Your suspending function has the superpower to suspend execution without blocking the thread, but onCreate
is just normal function and don’t possess that superpower. So naturally, it cannot call a suspending function.
Suspending functions can only be called from another suspending function, from a suspending lambda, from a coroutine, or from a lambda that is inlined into a coroutine. The Kotlin compiler will give you a friendly reminder of this when you try to call a suspending function differently. Intuitively, you have to enter the world of suspending functions first. This is done using coroutine builders.
Coroutine Builders
Coroutine builders open the gate to the world of suspending functions. They are the starting point to spin up a new coroutine and to make an asynchronous request. As mentioned, coroutine builders accept a suspending lambda that provides the coroutine’s task. There are three essential coroutine builders with different intentions:
launch
is used for fire-and-forget operations without return valueasync
is used for operations that return a result (or an exception)runBlocking
blocks the current thread and waits for the coroutine to finish execution.runBlocking coroutine builder. Following listing demonstrates how to use runBlocking
to be able to call suspending functions from onCreate
.
override fun onCreate(savedInstanceState: Bundle?) { ... runBlocking { updateWeather() } }
As a coroutine builder, runBlocking
creates and starts a new coroutine. Living up to its name, it blocks the current thread until the block of code passed to it completes. The thread can still be interrupted by other threads, but it cannot do any other work. This is why runBlocking
should never be called from a coroutine; coroutines should always be nonblocking. In fact, runBlocking
is meant to be used in main functions and in test cases for suspending functions.
launch coroutine builder. Intuitively, the launch
coroutine builder is used for fire-and-forget coroutines that execute independent of the main program. These are similar to threads in that they are typically used to introduce parallelism and uncaught exceptions from the coroutine are
printed to stderr
and crash the application. Following listing gives an introductory example.
// Launches nonblocking coroutine launch { println("Coroutine started") // 2nd print delay(1000) // Calls suspending function from within coroutine println("Coroutine finished") // 3rd print } println("Script continues") // 1st print Thread.sleep(1500) // Keep program alive println("Script finished") // 4th print
This code launches one new coroutine that first delays for a second — in a nonblocking way — and then prints a message. The delay
function is a suspending function from the standard library and thus represents a suspension point. However, even the print statement before the delay is only printed after the print statement that follows launch
.
async coroutine builder. The next essential coroutine builder is async
, which is used for asynchronous calls that return a result or an exception. This is highly useful for REST API requests, fetching entries from a database, reading contents from a file, or any other operation that
introduces wait times and fetches some data. If the lambda passed to async
returns type T
, then the async
call returns a Deferred<T>
. Deferred
is a lightweight future implementation from the Kotlin coroutines library. To get the actual result, you must call await
on it.
await
waits for the completion of the async task - of course nonblocking - to be able to return the result. Kotlin has no await
keyword, it’s just a function. Followng listing demonstrates how easily async
allows you to make multiple asynchronous calls simultaneously (if they’re independent).
// Return type is Deferred<Int> fun fetchFirstAsync() = async { delay(1000) 294 // Return value of lambda, type Int } // Return type is Deferred<Int> fun fetchSecondAsync() = async { delay(1000) 7 // Return value of lambda, type Int } runBlocking { // Asynchronous composition: total runtime ~1s val first = fetchFirstAsync() // Inferred type: Deferred<Int> val second = fetchSecondAsync() // Inferred type: Deferred<Int> val result = first.await() / second.await() println("Result: $result") // 42 }
The two asynchronous functions both start a new coroutine and simulate network calls that each take 1 second and return a value. Because the lambdas passed to async
return Int
, the two functions both return a Deferred<Int>
. Following naming conventions, the function names carry an "async" suffix so that asynchrony is explicit at the call site and a Deferred
can be expected. Calling first.await()
suspends execution until the result is available and returns Int
, the unwrapped value. So when the code reaches the expression first.await()
, it will wait for about a second before moving on to second.await()
, which requires no further waiting at that point because that asynchronous job is also finished after 1 second.
The two asynchronous functions used so far are not idiomatic Kotlin code. Kotlin encourages sequential behavior as the default, and asynchrony as an optin option. Following listing shows the idiomatic approach.
suspend fun fetchFirst(): Int { delay(1000); return 294 } suspend fun fetchSecond(): Int { delay(1000); return 7 } GlobalScope.launch(Dispatchers.Main) { val a = async(Dispatchers.IO) { fetchFirst() } // Asynchrony is explicit val b = async(Dispatchers.IO) { fetchSecond() } // Asynchrony is explicit println("Result: ${a.await() / b.await()}") } // or suspend fun fetchAll(): Int = coroutineScope { val deferred1 = async { fetchFirst(str) } val deferred2 = async { fetchSecond(str) } deferred1.await() / deferred2.await() }
Notice that the two fetch functions are now suspending functions, have the natural return value, and there is no need for an "async" suffix. This way, they behave sequentially by default and you can opt in to asynchrony simply by wrapping the call into async { ... }
. This keeps the asynchrony explicit.
Notice the similar relations between launch/join
and async/await
. Both launch
and async
are used to start a new coroutine that performs work in parallel. With launch
, you may wait for completion using join
, whereas with async
, you normally use await
to retrieve the result. While launch
returns a Job
, async
returns a Deferred
. However, Deferred
implements the Job
interface so you may use cancel
and join
on a Deferred
as well, although this is less common.
Coroutine Dispatchers
The dispatcher sends off coroutines to run on various threads. Whenever a coroutine resumes, its dispatcher decides on which thread it resumes. Following listing shows how to pass a CoroutineDispatcher
to launch a coroutine on the UI thread on Android.
kotlinx.coroutines
provides following dispatchers:
Dispatchers.Default
. Used by all standard builder if no dispatcher is specified. It uses a common pool of shared background threads. This is an appropriate choice for compute-intensive coroutines that consume CPU resources.Dispatchers.IO
. Uses a shared pool of on-demand created threads and is designed for offloading of IO-intensive blocking operations (like file I/O and blocking socket I/O).All coroutines builders like launch
and async
accept an optional CoroutineContext
parameter that can be used to explicitly specify the dispatcher for new coroutine and other context elements. So, launch(Dispatchers.Default) {}
uses the same dispatcher as GlobalScope.launch {}
.
import kotlinx.coroutines.android.UI import kotlinx.coroutines.launch launch(UI) { updateUi(weatherData) }
Before resuming a coroutine, the CoroutineDispatcher
has a chance to change where it resumes. A coroutine with the default dispatcher always resumes on one of the CommonPool
threads and therefore jumps between threads with every suspension.
In many cases, you want more finegrained control of the context. More specifically, you want different parts of a coroutine to run on different threads. A typical scenario is making asynchronous calls to fetch data on a background thread and then displaying it in the UI once it is available. The updateWeather
function presented above resembles this pattern. A better implementation for it is given in following listing.
suspend fun updateWeather(userId: Int) { val user = fetchUser(userId) val location = fetchLocation(user) val weatherData = fetchWeather(location) withContext(UI) { updateUi(weatherData) } } launch { updateWeather() }
Running part of a coroutine in a specific context is done using withContext
. That way, only the code that touches the UI is executed on the UI thread. The suspending function itself can now be launched in a thread pool to perform the asynchronous calls without affecting the UI thread at all until the final result is available. Note that context switching between coroutines is far less expensive than context switching between
threads if the coroutines run in the same thread. However, if they are in different threads (like here), switching coroutine contexts requires an expensive thread context switch.
Following is another example of withContext
.
GlobalScope.launch(Dispatchers.Main) { val user = withContext(Dispatchers.IO) { userService.doLogin(username, password) } val currentFriends = async(Dispatchers.IO) { userService.requestCurrentFriends(user) } val suggestedFriends = async(Dispatchers.IO) { userService.requestSuggestedFriends(user) } val finalUser = user.copy(friends = currentFriends.await() + suggestedFriends.await()) }
A Coroutine’s Job
Basically, a job is anything that can be canceled. Every coroutine has a job, and you can use the job to cancel the coroutine. Jobs can be arranged into parent-child hierarchies. Canceling a parent job immediately cancels all the job's children, which is a lot more convenient than canceling each coroutine manually.
The Job
also provides access to important properties of the job execution. The property isCompleted
is one of these, and similarly there are also isCancelled
and isActive
.
Coroutine Scopes
A coroutine's scope defines the context in which the coroutine runs. A scope combines information about a coroutine's job and dispatcher. Scopes keep track of coroutines. When you launch a coroutine, it's "in a scope," which means that you've indicated which scope will keep track of the coroutine.
Each coroutine run inside a scope defined by us, so we can make it application wide or specific for a view with well-defined life-cycle such as Activities or Fragments. Every coroutine builder is an extension on CoroutineScope
. Each coroutine waits for all the coroutines inside their block/scope to complete before completing themselves.
A CoroutineScope keeps track of all your coroutines, and it can cancel all of the coroutines started in it.
There are two types of scopes.
coroutineContext
property. Here, there are at least two important things to configure: the dispatcher, and the job.So CoroutineScope
provides properties like coroutineContext
and it is a set of various elements like Job
of the coroutine and its dispatcher. We can check whether coroutine is active or not using isActive property
of Job
. GlobalScope
is used to launch a coroutine with a lifetime of the whole application.
All coroutine builders accept a CoroutineContext
. This is an indexed set of elements such as the coroutine name, its dispatcher, and its job details. In this regard, CoroutineContext
is similar to a set of ThreadLocals
, except that it’s immutable.
The two most important elements of a coroutine context are the CoroutineDispatcher
that decides on which thread the coroutines run, and the Job
that provides details about the execution and can be used to spawn child coroutines. Apart from these two, context also contains a CoroutineName
and a CoroutineExceptionHandler
.
Scopes in Kotlin Coroutines are very useful because we need to cancel the background task as soon as the activity is destroyed. Here, we will learn how to use scopes to handle these types of situation.
ViewModel scope. Coroutines in this scope are useful when there is work that should only be done when a ViewModel is active.
To start a coroutine, we would need to create the scope like so:
class MainViewModel : ViewModel(){ private lateinit var job: Job private lateinit var coroutineScope: CoroutineScope private fun initialize(){ // create job & scope job = Job() coroutineScope = CoroutineScope(Dispatchers.Main + job) } }
In the code snippet above, the coroutineScope
will be started in the main thread (we know this because we specified that the dispatcher should be Dispatchers.Main).
To cancel this coroutine, it’s a lot easier because we only have to do it once. We may want to cancel once the view model is no longer used (when the activity or fragment using the view model is no longer active).
override fun onCleared(){ super.onCleared() job.cancel() // anything else you'd like to do }
Once we cancel
job, all the coroutines launched by coroutineScope
will be canceled as well. Canceling jobs help us prevent coroutine leaks.
Also, to use coroutines in a ViewModel
, you can use the viewModelScope
extension property from lifecycle-viewmodel-ktx
.
To avoid boilerplate, add the code below to your build.gradle file.
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$view_model_scope_version"
This library adds viewModelScope
as an extension function and binds the scope to Dispatchers.Main
by default. However, the dispatcher can be changed if need be. Take a look at this example:
class MyViewModel(): ViewModel() { fun userNeedsDocs() { // Start a new coroutine in a ViewModel viewModelScope.launch { fetchDocs() } } }
viewModelScope
will automatically cancel any coroutine that is started by this ViewModel
when it is cleared (when the onCleared()
callback is called).
Activity scope. Assuming that our activity is the scope, the background task should get canceled as soon as the activity is destroyed.
In the activity, we need to implement CoroutineScope
.
class MainActivity : AppCompatActivity(), CoroutineScope { override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job private lateinit var job: Job }
In the onCreate
and onDestory
function.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) job = Job() // create the Job } override fun onDestroy() { job.cancel() // cancel the Job super.onDestroy() }
Now, just use the launch
like below:
launch { val userOne = async(Dispatchers.IO) { fetchFirstUser() } val userTwo = async(Dispatchers.IO) { fetchSeconeUser() } showUsers(userOne.await(), userTwo.await()) }
As soon as the activity is destroyed, the task will get cancelled if it is running because we have defined the scope.
When we need the global scope which is our application scope, not the activity scope, we can use the GlobalScope
as below:
GlobalScope.launch(Dispatchers.Main) { val userOne = async(Dispatchers.IO) { fetchFirstUser() } val userTwo = async(Dispatchers.IO) { fetchSeconeUser() } }
Here, even if the activity gets destroyed, the fetchUser
functions will continue running as we have used the GlobalScope
.
Lifecycle scope
A LifecycleScope
is defined for each Lifecycle object. LifecycleOwner
could be an Activity or a Fragment. Any coroutine launched in this scope is canceled when the Lifecycle is destroyed. You can access the Lifecycle’s CoroutineScope either via lifecycle.coroutineScope
or lifecycleOwner.lifecycleScope
properties. To use this, add the following dependency to your build.gradle file.
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_scope_version"
Launching a coroutine using this scope can be done like this:
class HomeFragment: Fragment() { ... private fun doSomething(){ lifecycleOwner.lifecycleScope.launch { // Coroutine launched. Do some computation here. } } }
This is a lot cleaner as opposed to writing boilerplate when you want to launch a coroutine.
Exception Handling
launch. For this we need to create an exception handler like below:
val handler = CoroutineExceptionHandler { _, exception -> Log.d(TAG, "$exception handled !") }
Then, we can attach the handler like below:
GlobalScope.launch(Dispatchers.IO + handler) { fetchUserAndSaveInDatabase() // do on IO thread }
If there is any exception in fetchUserAndSaveInDatabase
, it will be handled by the handler which we have attached.
When using in the activity scope, we can attach the exception in our coroutineContext
as below:
class MainActivity : AppCompatActivity(), CoroutineScope { override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job + handler private lateinit var job: Job }
And use like below:
launch { fetchUserAndSaveInDatabase() }
async. When using async
, we need to use the try-catch
block to handle the exception like below.
val deferredUser = GlobalScope.async { fetchUser() } try { val user = deferredUser.await() } catch (exception: Exception) { Log.d(TAG, "$exception handled !") }
How to launch a coroutine with a timeout
If you want to set a timeout for a coroutine job, wrap the suspended function with the withTimeoutOrNull
function which will return null in case of timeout.
val uiScope = CoroutineScope(Dispatchers.Main) val bgDispatcher: CoroutineDispatcher = Dispatchers.I0 fun loadData() = uiScope.launch { view.showLoading() // ui thread val task = async(bgDispatcher) { // background thread // your blocking call } // suspend until task is finished or return null in 2 sec val result = withTimeoutOrNull(2000) { task.await() } view.showData(result) // ui thread }
How to get object from Firebase database
Create funtion to fetch object
suspend fun getPerson(id: String) = suspendCoroutine<Person?> { cont -> val ref = Utils.getDB().child(Constants.FIREBASE_USERS_DB).child(id) ref.addValueEventListener(object: ValueEventListener { override fun onDataChange(dataSnapshot: DataSnapshot) { if (dataSnapshot.exists()) { val p = dataSnapshot.getValue(Person::class.java)!! cont.resume(p) } else { cont.resume(null) } ref.removeEventListener(this) } override fun onCancelled(databaseError: DatabaseError) { cont.resume(null) Log.d(Constants.TAG, "ERROR getPerson: ${databaseError.message}") } }) }
Launch it
GlobalScope.launch(Dispatchers.Main) { val person = Repository.getPerson(id)!! ... }
Actors
An actor is a combination of a coroutine state, behavior and a channel to communicate with other coroutines. Actor processes stream of messages. It can also be used for tasks that should not be performed concurrently. This is useful when we do not want to respond to all back-to-back (unnecessary) clicks by a user on a button but only first or recent click.
Channels
Channel is a non-blocking primitive for communication between sender and receiver. It has suspending operations instead of blocking ones. It provide a way to transfer a stream of values between coroutines.
Useful links