Buy Glassdoor Reviews

Do you want to know what your employees think of you? Then you can check out employee rating websites like Glassdoor; You may be surprised. In today’s competitive job market, you will not be able to…

Smartphone

独家优惠奖金 100% 高达 1 BTC + 180 免费旋转




Coroutines basics

We developers are constantly on a lookout for new solutions, the best approaches so no wonder the world is changing and asynchronous programming is changing with it. At Fandom we do the same: we want to follow and apply the best solutions for specific problems. And that’s what we did for one of our latest projects. We decided to use Kotlin Coroutines in our Android app, not only because they are native (less dependencies, loosely coupled) but especially their usage has certain benefits.

It doesn’t matter if you write mobile or desktop or even server-side applications, there could be a place for Kotlin Coroutines in your code. But what actually are they? How do they work? What problem do they solve? Where and when should they be used? In this article I would like to answer all of these questions. If you have never heard about them or you know a little this blog post is for you.

Coroutines by definition are light-weight threads. So what does it actually mean? We can divide the name into two parts: coroutines = co + routines. So we can think about coroutines as cooperative (co) functions (routines) working on threads. In this context, cooperative means that functions can work together on shared tasks.

In the above example task is performed by cooperation of functionA and functionB. It’s worth noticing that, at some point, there is a need to switch the thread. Coroutines allow us to easily write multithreaded asynchronous code in a synchronous way. In other words, coroutines are a sequence of subtasks executed in specific order.

As mentioned above “coroutines are light-weight threads”, but they are not threads at all! They can run in parallel, communicate and wait for each other just like threads, but, unlike them, they are very cheap in terms of performance. We can think about coroutines as a framework of efficient work on threads.

At this point you only need to know that runBlocking is a coroutine builder function that blocks the current thread until all tasks in a coroutine are completed. This one could be useful for writing tests that need to be paused or learning examples just like this one. If some keywords are not clear, don’t worry, we will talk about them later.

Okay, now we know what coroutines are and their biggest advantage versus threads but where is the place to use them? It’s time to look closer at ways of running asynchronous code based on an example.

The traditional model of creating asynchronous code based on callback methods is subject to various difficulties. In the case of execution of concurrent dependent tasks, the problem of communication between tasks may arise, which forces finding a way to synchronize the results, thus increasing the complexity of the code. Another problem is the nesting of callback methods, which in turn means that tasks are actually performed synchronously (pyramid of doom), in the result extending their processing time.

There is also a reactive approach offered by RxJava, a powerful, complex library which has been a rescue for many for a long time. It gets rid of nested callbacks by implementing the reactive programming paradigm.

Coroutines make concurrent tasks much easier by changing the style of writing asynchronous code sequentially. Thanks to this approach, the code is more readable and understandable and task management becomes easier. In addition, one thread can handle many coroutines simultaneously, which translates into a significant increase in efficiency. Coroutines allow us to avoid the callback hell and are a good alternative to RxJava.

One of the key concepts of coroutines is the idea of a suspend function. This kind of function can suspend its operation until later resume without blocking the thread (for example waiting to resume for the end of another function result). The cost of suspension in relation to blocking the thread is much lower. A suspend function must be called inside coroutine or in another suspend function on any thread.

Creating and executing a coroutine can be done by one of the builder functions like: runBlocking, launch, async (you could have noticed some of them before). Actually, in practice, coroutines are mostly created by coroutine builders. Builder functions are not suspend functions, they just create a new coroutine. Also, coroutines are built and work within structured concurrency which means that a parent has the ability to influence their children.

launch is used for fire and forget tasks in which the result is not expected. It returns a handler to its coroutine as a Job type object, thanks to which it is possible to manually manage the state of the coroutine. join method blocks the related coroutine until all its tasks are completed, while cancel just cancels the whole coroutine.

async, similarly to launch, allows for parallel execution of tasks. However, it returns an object of the Deferred (extends Job) type which is a promise of a future result. So when tasks are independent of each other and return some result, use async to get things faster. await suspends further execution of the statement until a result is obtained.

Coroutine execution can be confined to a specific thread, run unconfined or dispatched to a thread pool. CoroutineDispatcher object helps to decide on which thread a coroutine runs. To do that, simply just use one of the defined dispatchers in Dispatchers class.

Default is a default dispatcher that can run on a shared pool of threads and should be used for expensive CPU work such as calculations. IO is mainly used for some I/O operations like network call, database or file access. Main runs on the main thread — for Android it’s UI thread. Unconfined starts a coroutine in the caller thread and, after the first suspension, it resumes on the thread defined by the suspending function that was invoked. newSingleThreadContext creates a context with a dedicated thread, which is very expensive so use it carefully.

Every coroutine must be executed in some context represented by a value of the CoroutineContext type, which is a set of rules and configuration that defines how coroutine is executed. It can also be a combination of objects of different context types ( CombinedContext). CoroutineContext consists of objects of Job, CoroutineDispatcher and CoroutineExceptionHandler types and can be passed explicitly or derived implicitly from the scope in which it is being executed. If you want to stay in the same coroutine but do work on a different thread, just call withContext builder function to switch the context in the same coroutine.

You could have noticed that GlobalScope.launch was written in some place before. That’s because coroutines work within a certain scope, which is their execution space and implementation of the structured hierarchy. Actions taken on a scope affect all coroutines within it. Thanks to this, the problem of manual management of the status of all coroutines is eliminated. For example, instead of manually canceling every coroutine just simply cancel them through their scope. GlobalScope is a scope referenced to the whole app lifecycle, so try to avoid it if possible. In order to define your own scope for any class just implement CoroutineScope and override coroutineContext.

Fortunately, the androidx.lifecycle library provides extensions for some components that create coroutine scope for you, so there is no need to implement CoroutineScope for each Activity or ViewModel.

When cancel is called on a scope or a job, it doesn’t mean that it happens immediately. Moreover it could never happen — the code must be cancelable. If a coroutine has been canceled, a CancellationException exception is thrown and the operation aborts. However, if a block of code is not inside the suspend function (each one is cancellable), there is no auto check of the running state, which causes the code to not respond to cancel requests. To make cancellation possible, check if it has been canceled manually through isActive property or yield function. To set timeout execution use withTimeout and withTimeoutOrNull functions.

A single coroutine throws CancellationException to cancel itself and this is expected behaviour and we don’t have to worry about it. But what happens if another exception was thrown? How to handle it? It depends. Propagating exceptions can be done automatically (e.g. for launch) or by exposing them to users (e.g. async). The first thing we need to know is that CoroutineExceptionHandler, which is a part of scope, invokes only on uncaught exceptions. Coroutines created in the context of Job — by launch, delegate handling of their exceptions to their parent and so on until root coroutine. So CoroutineExceptionHandler is ignored for all children coroutines and makes sense only for root. Moreover, coroutines created by async always catch exceptions and attach them in the Deferred result so CoroutineExceptionHandler doesn’t apply either. When multiple children coroutines fail with an exception, only the first one is handled.

Cancellation is bidirectional and propagates through the whole coroutine hierarchy. So one uncaught exception from any child cancels the whole root coroutine. If an opposite unidirectional relationship is required, SupervisorJob can be used as a part of coroutine scope, or just supervisorScope as a scope itself (instead of coroutineScope). For that case, every child should handle its exceptions on their own because any failure doesn’t propagate to the parent.

In this blogpost I have attempted to unravel some of the mystery behind Kotlin Coroutines. I have demonstrated the what, where and how of coroutines and presented what benefits they bring to the table, the most important of which is undoubtedly performance. Coroutines, in fact, make it easy to write performant and thread-safe code and, because they are native to Kotlin, can be considered to be the best solution for asynchronous code, especially if you create a new app. However, this blogpost covers only just the surface of what Kotlin Coroutines have to offer and beyond is where really exciting stuff happens, so stay tuned for more to come.

Add a comment

Related posts:

Experienced Personal Injury Attorney

Looking for an attorney for personal injury in Bellingham? Law Office of Benjamin A. Pepper, PLLC representing injury clients throughout Washington. Get help now by calling us @ (360) 733–3966 or…

Tales From the Crib

In a world full of misinformation, I believe it is important to set the record straight. Future generations of historians will be combing through our incessant bullshit, searching for random corns of…

Why I studied Biomedical Engineering

As an Asian female immigrant/ international student, I want to break out of the stereotypes and believe in myself to make a difference in the world. And to make a difference, I want to understand how…