What are corutines in the Basin?

Radosław Kondziołka
Calendar icon
17 grudnia 2020

With version 1.3, coroutines were introduced into Kotlin. The concept of coroutines itself is by no means new in the world of programming and is already present in many languages. In a way, coroutines offer a slightly different approach to concurrent programming.

The first coroutine

In Kotlin, coroutines are created using so-called coroutine builders, one of whose representatives is the launch used below. This function creates a coroutine within which a block of code passed as a parameter is executed:

1fun main(args: Array<String>) { runBlocking { launch { delay(1000) println("The first coroutine was executed") } } } }

runBlocking acts as a certain convenience here and should not be focused on. At this stage, it is enough for us to know that it allows the coroutine to be executed in the current thread. A coroutine created in this way waits a second, then prints the text to the screen, after which it finishes its operation. Coroutines are often referred to as light threads to indicate the lightness of their creation and execution. Similar to functions, coroutines are executed within a thread, whereby a single thread can "simultaneously" execute multiple coroutines. There is a context(CoroutineContext) associated with eachcoroutine. And it depends on it, in which thread the coroutine will be executed.

Coroutine context

Coroutines always work in some context. As already mentioned, the context determines how the coroutine will be executed and maintains a reference to the job (Coroutine Job) , which represents the block of code being executed within the coroutine. Using the job, which is returned by the create function, you can cancel the execution of the coroutine:

1runBlocking { val job = launch { println("Starting") delay(1000) println("Exiting") } delay(500) job.cancel() }

Only the first message was printed on the screen, as the coroutine was canceled.

Another important context element is the Dispatcher, which determines the thread that executes the coroutine. Kotlin provides several dispatchers with different uses. One such dispatcher is Dispatchers.IO, which is mainly intended for performing I/O operations. The following shows how we can specify which Dispatcher we want to use to execute our coroutine:

1runBlocking { launch(Dispatchers.IO) { println("A coroutine on thread ${Thread.currentThread().name}") } } }

If you don't specify a context in the create function, it is inherited from the current scope, about which more below.

CoroutineScope

CoroutineScope is, in a sense, a way to structure the execution ofcoroutines. Scopes have a hierarchical structure, i.e. there is a parent-child relationship between them. Coroutines running in a scope that is in the parent role wait for coroutines that are running in a scope that is in the child role to finish their work before they finish their work. The hierarchical relationship between coroutines (and, in fact, between jobs associated with coroutines) makes it possible to propagate cancellation operations from parents to children. Below is a short example showing the hierarchical structure between two coroutines:

1GlobalScope.launch { println("I am a parent coroutine") launch { println("I am a child coroutine") delay(1000) }.invokeOnCompletion { println("Completion of a child coroutine") } } }.invokeOnCompletion { println("Completion of a parent coroutine") } }

Above, the external coroutine has been placed in the so-called GlobalScope, which is the global scope in the application. The inner coroutine inherits the context from the outer coroutine and becomes a child of the outer coroutine. The result of the above program.

1I am a parent coroutine I am a child coroutine Completion of a child coroutine Completion of a parent coroutine

betrays the consequence of this state of affairs: the external coroutine as a parent is waiting for the completion of a child coroutine.

Coroutines, what are they actually?

At this stage, it doesn't look like the execution of coroutines is somehow particularly different from the execution of ordinary functions. As a matter of fact, it does, except that the execution of korutins can be halted at special points - so-called suspension points. Such suspension consists in interrupting the execution in the current thread of the current coroutine, but in a way that does not block the thread that executes this coroutine. Such a thread can occupy itself, for example, with executing the code of another coroutine. Execution of the coroutine interrupted in this way will then continue from the point of pause. Let's look below at the following code and the result of its execution result:

1runBlocking { launch { println("Start #1 on thread ${Thread.currentThread().name}") yield() // suspension point println("Exit #1 on thread ${Thread.currentThread().name}") } launch { println("Start #2 on thread ${Thread.currentThread().name}") println("Exit #2 on thread ${Thread.currentThread().name}") } } }
1Start #1 on thread main Start #2 on thread main Exit #2 on thread main Exit #1 on thread main

In this case, the yield function plays the role of the pause point. The observed flow is as follows:

  1. The first instruction from the first coroutine is executed.
  2. The coroutine at the pause point is paused.
  3. The second coroutine is fully executed.
  4. The previously paused coroutine is resumed.

This clearly indicates a "context switch" that has occurred within a single thread. It is worth noting here that - in general - good candidates for such pause points are operations that are inherently blocking operations, such as network communications. For example, when a network query is executed, the coroutine is paused and another coroutine can be executed on the current thread. This does not mean at all that the network query is not executed. Its execution may have been implemented in a non-blocking way or may have been postponed to another thread. Suspend points can be placed either directly in the body of the coroutines or in suspend functions, which we will look at in the next section.

Suspend functions

Suspend functions are functions whose execution can be suspended at certain points (suspension points) and then continued. Functions of this type can only be executed within a coroutine. It can be said that suspend functions are the basic building blocks from which coroutines are constructed and coroutines act as "executors" with respect to these functions. In Koltin, the definition of such functions is introduced with the dedicated keyword suspend.

1suspend fun prepareAgreement(templateId: String, customer: Customer) : Agreement { val request = prepareRequest() val template = client.getTemplate(request) // do HTTP GET request (suspension post) return createAgreement(template, customer) }

Below is a graphic showing the execution of such a program:

obraz1.webp

The green color indicates the prepareAgreement function while the blue color indicates some other coroutine. While fetching the agreement template from another service, the T1 thread executes another coroutine without wasting resources. We assume here that we are using a library that can perform HTTP requests in a non-blocking manner.

Summary

Programming using coroutines may seem very easy on the surface. However, you need to be very careful with it, because an improperly implemented coroutine can negatively affect the execution of other coroutines from the same context, which can result, for example, in reduced responsiveness of the application. Nevertheless, the ability to program based on coroutines is a very useful skill in a programmer's workshop.

Read also

Calendar icon

27 wrzesień

Omega-PSIR and the Employee Assessment System at the Warsaw School of Economics

Implementation of Omega-PSIR and the Employee Evaluation System at SGH. See how our solutions support university management and resea...

Calendar icon

12 wrzesień

Playwright vs Cypress vs Selenium: which is better?

Playwright, Selenium or Cypress? Discover the key differences and advantages of each of these web application test automation tools. ...

Calendar icon

22 sierpień

A new era of knowledge management: Omega-PSIR at Kozminski University

Kozminski University in Warsaw, one of the leading universities in Poland, has been using the Omega-PSIR system we have implemented t...