Curious about Kotlin coroutine but don't know how to get started? read this!
Let's start from the basics by writing the very simple block of code to get our hands on Kotlin coroutine. You will learn these basic concepts to start coding in less than 5 minutes:
- Suspended functions
- Context and scope
- Builders
- Channels
There are many more awesome features, but today we will focus on the most important ones.
Suspended functions
private suspend fun doACoroutineTask(someValue:Int) ;
suspend
is a keyword that defines the coroutine. It means that this function can be paused and resumed. It can execute a long-running operation and wait for it to complete without blocking.
Under the hood, suspend functions are converted by the compiler to another function without the suspend keyword, that takes an additional parameter of type Continuation.
Context and scope
coroutineScope {
launch(CoroutineName("SomeName")) {
doACoroutineTask(taskInput)
}
}
Context objects hold information that is necessary for the flow and tasks/coroutine. Context is a persistent indexed dictionary that maps from a CoroutineContext.KEY to a CorountineContext.ELEMENT. There are many context options and they all implement the context interface kotlin.coroutines.CoroutineContext
. In the example we used CoroutineName
which holds the name for the coroutine, it is one of the more simple context objects that we can use.
An important feature is the +
operator. we can join two contexts together to build a bigger context with more entries. But, we need to be aware that we will lose keys if there are key duplication.
Context is used within a scope, scope object defines the scope for the coroutines and receives a context to start the scope, if no context is given, the scope takes the empty context element. To run a suspended function we have to call it within a scope. In the example, we use the basic scope named coroutineScope
and we call a launch function that launches doACoroutineTask
with a context.
Builders are bridges
Coroutine Builders are an extension functions on CoroutineScope and they inherit the CoroutineContext of the scope they invoked in. They are simple functions that create a new coroutine to run a given suspending function. They act as bridges between the non-coroutine world and the coroutine world. We call the builders from the non-suspending/non-coroutine world to start the suspending world. Popular examples are async
and launch
.
launch: Launches a new coroutine in the background and returns a reference to it as a Job object. The launch coroutine builder is a "fire and forget" since it does not return any result to the caller asides the job instance which is used to handle the background operation. It inherits the context and job from the scope where it was called but these can be overridden.
async: Creates a coroutine and returns its future result as an implementation of Deferred, which can be resembled to promise
, where the task promise to return a result and in the meantime it is suspended in order to utilize the CPU for other tasks. By default, this builder created a coroutine that is immediately scheduled for execution.
coroutineScope {
launch(CoroutineName("SomeName")) {
doACoroutineTask(taskInput)
}
}
In the example, we launch a coroutine(suspended function) task using the launch
builder within a simple scope.
Channels
Channel is a non-blocking primitive for communication between sender using SendChannel
and receiver using ReceiveChannel.*
. Conceptually, a channel is similar to java.util.concurrent.BlockingQueue
but it has suspending operations instead of blocking ones and it can be closed. We can use channels for sharing information between coroutines. Channel is not part of the coroutine concept but in Kotlin we can use it together to have better functionality and usage. For example:
val ordersChannel = Channel<Order>()
val job = launch {
for (order in orders) {
ordersChannel.send(order)
}
ordersChannel.close()
}
}
Here we create an orders channel to receive orders, we use this channel to send the orders to the coroutine. We actually didn't use it yet we only defined its input- which is orders
from a list of orders.
private suspend fun doACoroutineTask(ordersChannel :ReceiveChannel<Order>){
for(order in ordersChannel){
// do some suspending task
sleep(5)
}
}
....
coroutineScope {
launch(CoroutineName("SomeName"){
doACoroutineTask(ordersChannel)
}
}
In the second block, we define a suspended function that takes channel as input. The function goes over the channel and process the input by calling a suspending task. In the example, the suspending task is 5 milliseconds sleep. And at last, we call the coroutine from a coroutineScope
and use the launch builder to bridge between the coroutine world and the main world. From the builder, we call the suspended function and providing it the relevant channel.
Now You do it 💪 :
- Go to github repository
- Press the watch and star buttons
- Clone the project
- Open your IDE and import project using the
build.gradle
file ( 10 seconds video on this coming up soon ) - Play with the different main functions and explore the code.
Now you know all the basics that you need to start working with Kotlin coroutines.
In the next post we will talk about concurrency and how coroutine works with threads.
🐦 Follow me on Twitter, happy to take your suggestions on topics.