What phenomenon are coroutines? These are basically lightweight threads providing better use of the apps they are operating on. Furthermore, there’s no need to manually control the threads performing them.
The developer stays away from the lifespan of threads as new abstractions added by coroutines ensure context switching between tasks. Thus, the operational time of the threads is used more rationally.
It doesn’t take long to finally get a result from a notoriously sluggish input/output task. On the contrary, it keeps on processing tasks issued by other coroutines.
A developer’s typical work day involves code building and each one takes around 3-5 minutes. Meanwhile, they check emails and Twitter, do some stretching, or simply chillax, you name it. This is where coroutines step in. You get the most out of your time while waiting for the build to complete.
Moving forward, the idea of Kotlin coroutines is not brand new. A lot of programming languages make use of them, such as Go (goroutines), Erlang, and Java (Project Loom).
Only a few keywords are integrated with the Kotlin library. This is not a big deal. You simply add a dependency on kotlinx-coroutines-core
in order to enter coroutines. That wouldn’t take much effort. Check it for yourself.
Gradle:
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
}
Maven:
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.6.4</version>
</dependency>
</dependencies>
As long as the library gets updated, variations alter.
Let’s consider a limited-scope example below to get familiar with coroutines. Keep in mind that most of the upsides aren’t shown by this pattern. The more you learn about coroutines, the better you realize their benefits.
First, check this out:
fun main() = runBlocking {
launch {
delay(2000)
println("and it worked for me!")
}
print("My first coroutine, ")
}
Here a code activates a coroutine with a delay
before proceeding to the next step - printing out. The laconic example contains a couple of functions to hold your attention. Take a close look at them.
runBlocking
- runs a new coroutine that blocks the ongoing thread until its completion. In other words, the thread that runs it gets blocked for the duration of the call, until all the coroutines inside complete their execution. The pattern here only illustrates the example.launch
- is a so-called coroutine builder. It launches a new coroutine without blocking the current thread. Additionally, launch
inherits the CoroutineContext
from the present CoroutineScope
.delay
- suspends the coroutine for a time. It temporarily halts coroutines from lasting as Thread.sleep
does. But unlike that method, the underlying thread does not get blocked. Alternatively, the coroutine suspends. It releases its thread enabling another coroutine execution. A thread is obtainable when delay
is over, and the coroutine continues where it stopped.
A few words are to be explained.
CoroutineContext
- an interface that marks an element/set of elements. It determines the context in which the coroutine runs.CoroutineScope
- designates the scope for new coroutines that is utilized to track their lifecycle. It is composed of CoroutineContext
of the recent coroutine activated inside it. Specifically, extension functions of CoroutineScope
produce new coroutines. As you can see from the example, runBlocking
is rendering its CoroutineScope
to the further call to launch
.
What kind of function is that?
Such a method has already been shown in the example above – delay
. The compositions of delay
are:
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
Luckily, Kotlin language contains the suspend
keyword. What is the point of it?
You are aware that the functions that suspend through their execution at a point are marked as suspend
. Honestly, it takes a thorough getting into details to provide a comprehensive explanation on the matter. Otherwise, figuring out when functions suspend may be really tangled.
When do you annotate the function with suspend
? Just go by the book, if a function is prefaced with suspend
, and the calling point is outside the coroutine.
Compare a calling point outside a coroutine:
suspend fun printAfterDelay() {
delay(2000)
println("and it work!")
}
To a calling point inside a coroutine on the other hand:
fun CoroutineScope.printAfterDelay() = launch {
delay(2000)
println("and it work!")
}
By the way, have you noticed that both examples are insignificant code refactoring of the original one?
The compiler will come in handy in various situations, this is the exact benefit of a keyword. Thus, compiling the below:
fun printAfterDelay() {
delay(2000)
println("and it work!")
}
Will eventually result in such an error:
Suspend function 'delay' should be called only from a coroutine or
another suspend function
Note that this is an instructive explanation before you technically advance into action and become an expert in coding.
Kotlin coroutines are considered lightweight threads, a means of providing program multithreading in the sense that they can be implemented without the use of context switching mechanisms by the operating system.
They split and output their underlying resources when a certain coroutine hits a suspension point. This enables efficient usage of application resources since threads don’t have to be blocked during long-term tasks.
Wrapping up, we had a look at a simple pattern of an easy coroutine and a suspending function. Lastly, we touched on the suspend
keyword and how to operate it.
The given examples don’t illustrate the real value of using coroutines. The article describes only the basics. Keep an eye out for further in-depth guidance on the subject.