Kotlin Coroutines

- 7 mins

Kotlin Coroutines

Kotlin v1.3 was released bringing coroutines for asynchronous programming. This article is a quick introduction to the core features of kotlinx.coroutines.

Let’s say the objective is to say hello asynchronously. Lets start with the following very classic code:

Start
Done
Hello

What happened within the 3 seconds when the created Thread was sleeping? The answer: nothing! The thread was occupying memory without being used! This is when coroutines the light-weight threads kick in!

First Coroutine

The following is a basic way to migrate the previous example to use coroutines:

Start
Done
Hello

The coroutine is launched with launch coroutine builder in a context of a CoroutineScope (in this case GlobalScope). But what are suspending functions ? coroutine builders ? coroutine contexts ? coroutine scopes?

Suspending functions

Suspending functions are at the center of everything coroutines. A suspending function is simply a function that can be paused and resumed at a later time. They can execute a long running operation and wait for it to complete without blocking.

The syntax of a suspending function is similar to that of a regular function except for the addition of the suspend keyword. It can take a parameter and have a return type. However, suspending functions can only be invoked by another suspending function or within a coroutine.

Under the hood, suspend functions are converted by the compiler to another function without the suspend keyword, that takes an addition parameter of type Continuation<T> . The function above for example, will be converted by the compiler to this:

Continuation<T> is an interface that contains two functions that are invoked to resume the coroutine with a return value or with an exception if an error had occurred while the function was suspended.

Coroutine Builders

Coroutine builders are simple functions to create a new coroutine; the following are the main ones:

* Deffered is a generic type which extends Job.

Building Coroutines

Lets use coroutines builders to improve the previous example by introducing runBlocking:

It is possible to do better ? Yes ! By moving the runBlocking to wrap the execution of the main function:

But wait a minute, the initial goal of having delay(2000L) was to wait for the coroutine to finish ! Let’s explicitly wait for it then:

Structured concurrency

In the previous example, GlobalScope.launch has been used to create a top-level “independent” coroutine. Why “top-level” ? Because GlobalScope is used to launch coroutines which are operating on the whole application lifetime. “Structured concurrency” is the mechanism providing the structure of coroutines which gives the following benefits:

Job lifecycle

Coroutine (Job) Lifecycle

Let’s apply this to our example:

Using the outer scope’s context

At this point, an option may be to move the inner coroutine to a function:

This works, but there is a more elegant way to achieve this: using suspend and coroutineScope

[main] Start
[DefaultDispatcher-worker-1] Hello1
[main] Done

The new scope created by coroutineScope inherits the context from the outer scope.

CoroutineScope extension vs suspend

The previous example (using suspend) can be rewritten using CoroutineScope extension:

[main] Start
[main] Done
[DefaultDispatcher-worker-1] Hello1

The output though is not the same! why? Here is the rules:

Coroutines always execute in some CoroutineContext. The coroutine context is a set of various elements. The main elements are the Job of the coroutine and its CoroutineDispatcher.

Dispatchers

CoroutineContext includes a CoroutineDispatcher that determines what thread or threads the corresponding coroutine uses for its execution. Coroutine dispatcher can confine coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined.

Coroutine builders launch, async and withContext accept an CoroutineContext parameter that can be used to explicitly specify the dispatcher for new coroutine (and other context elements).

Here is various implementations of CoroutineDispatcher:

[main] Start
[DefaultDispatcher-worker-2] Dispatchers.Default
[DefaultDispatcher-worker-1] Dispatchers.IO
[main] Dispatchers.Unconfined
[main] from parent dispatcher
[main] Done

(Dispatcher.IO dispatcher shares threads with Dispatchers.Default)

Coroutine Scope

Each coroutine run inside a scope. A scope can be application wide or specific. But why this is needed ?
Contexts and jobs lifecycles are often tied to objects who are not coroutines (Android activities for example). Managing coroutines lifecycles can be done by keeping references and handling them manually. However, a better approach is to use CoroutineScope.
Best way to create a CoroutineScope is using:

Start
[DefaultDispatcher-worker-1] doing something...
[DefaultDispatcher-worker-3] doing something...
[DefaultDispatcher-worker-2] doing something...
[DefaultDispatcher-worker-2] doing something...
Done

Only the first four coroutines had printed a message and the others were cancelled by a single invocation of CoroutineScope.cancel() in Activity.destroy().

Alternatively, we can implement CoroutineScope interface in this Activity class, and use delegation with default factory function:

Conclusion

Coroutines are a very good way to achieve asynchonous programming with kotlin.
The following is an (over)simplified diagram of coroutines structure while keeping in mind each Element is a CoroutineContext by its own:

coroutines structure

Source:

rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora opensource