Concurrency is not something that most people think about on a daily basis; however, it benefits most of us throughout our day. Whenever we ask our technological devices to perform multiple tasks, either within one application or across multiple applications, our device is using concurrency to make it happen. Thanks to concurrent programming, our devices are able to multitask at the same rate that we do.
I’ve recently been working for a client where we use concurrent coroutines for their Android application. In this post, I will discuss concurrent programming, and how we can use Kotlin Coroutines for Android application development.
Introduction to Concurrency
Concurrency is the ability for different parts of a program to be executed in partial order or out-of-order, without affecting the final outcome. To put it more simply, concurrency is generally considered to be cooperative multitasking.
Concurrency is often confused with parallelism. Where concurrency is the ability of an application to process more than one task at a time, parallelism is the ability of the application to execute multiple tasks simultaneously. Therefore, it is possible for an application to be concurrent, but not parallel.
There are various approaches to concurrent programming that are used during the development of concurrent applications, including actors, workers, futures/promises, callbacks, event-driven systems, threads, and coroutines.
Two Models of Concurrent Programming
Concurrent programming breaks down into two general models:
- Shared Memory: The shared memory model is when you have two threads running at the same time that share access to the same area of memory (also known as a variable). In the shared memory model, you have to place a lock on the variable to ensure that the threads cannot change it at the same time.
- Message Passing: The message passing model is when the two threads pass items back and forth that are independent of the authoritative item in memory.
In working with Android to create applications, there are currently two main approaches to concurrent programming: RxJava and Coroutines. Though there are a number of other options that are available for use, these two approaches are generally the most popular.
In another recent project, I was asked to create an application using reactive programming. Reactive programming is programming that relies on events instead of the order of lines in the code. As observable events come in, the application must read and react to each observable in order. RxJava is the Java implementation of reactive programming.
For this project, however, we wanted a more straight-ahead approach. Reactive programming is a paradigm that alters the imperative programming style, and we didn’t want to go that route. Therefore, we decided to use Coroutines for our project.
Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed. Basically, coroutines allow you to use basic imperative functions or methods that suspend the other coroutines while they are working and then return control of the process to the other coroutines once they are complete.
The first language to explore the use of coroutines was Simula in 1967. While coroutines were not originally supported in many high-level programming languages, they have recently begun appearing in various libraries with more frequency.
Coroutines look very similar to a threading model. However, where threading models typically require a lot of resources to launch a thread, coroutines are fairly cheap, allowing you to run several coroutines on a single thread.
In 2017, Android announced that Kotlin would become the supported and official Android development language. This announcement incentivized program developers to write new Android applications in Kotlin code and to expand existing Java applications using Kotlin. For this reason, my application development focused on Kotlin coroutines.
Luckily, Kotlin is simply a more concise version of Java code. Kotlin is a Java Virtual Machine (JVM) language that is, according to the Kotlin developers, “100% compatible with Java.” Like Java, Kotlin is strongly-typed and threaded. The main difference between Kotlin and Java is the type inference. Unlike Java, Kotlin uses type inference so that you don’t have to declare a type within the code if the program can infer it.
In 2018, Kotlin 1.3 was released with coroutines, making coroutines a core part of the language. However, while they are a part of the Kotlin code, coroutines must still be imported independently.
Kotlin coroutines are invoked using the coroutine building through the “launch” command within the Kotlin code. Though coroutines do not have to be run sequentially, they are executed sequentially by default.
One of the biggest challenges with coroutines is keeping track of the long-running tasks. As the code is altered, it is easy to forget about coroutines that will nonetheless continue to operate in the background, causing work leaks. These work leaks can lead to inefficiencies in the application long term. Kotlin handles work leaks in coroutines in a number of ways:
- Jobs: The first approach Kotlin uses to handle work leaks is Jobs. Every Kotlin coroutine builder will create an individual Job. If, in the future, a coroutine is no longer needed, it can be shut off through a simple “job.cancel” command instead of being manually deleted from the code.
- Scopes: The second approach Kotlin uses is Scopes. Once a scope is created, multiple coroutines can be launched within that scope. If you cancel the scope, it in turn cancels all of the associated coroutines. This is especially beneficial for Android applications, where you have frequent updates that create and destroy areas of code. If you only need coroutines during the lifetime of the area they are associated with, using scopes is a good way to control their activity.
- Coroutine Hierarchies: When coroutines are created within one another, they are linked in a parent-child relationship. This means that if the parent coroutine is cancelled or errors, the child coroutine is also cancelled. Conversely, if the child coroutine has an error, it bubbles up to the top of the hierarchy and shuts down the parent as well. This minimizes work leaks because only the parent coroutine needs to be cancelled to cancel all associated coroutines.
Thread Safety with Kotlin Coroutines
Though coroutines are not threads, thread safety is still a concern when using coroutines because they run on top of threads. Coroutines are run on top of threads with the help of dispatchers. Dispatchers confine coroutine executions to specific threads to help ensure thread safety. The primary dispatchers available with Kotlin coroutines are:
- Dispatchers.Main: This dispatcher lets you run the coroutine along the main thread. However, outside of extreme cases, running anything along the main thread is not advisable.
- Dispatchers.Default: This dispatcher lets you run the coroutine from a generic thread pool.
- Dispatchers.IO: This dispatcher lets you run the coroutine from a thread pool that is dedicated to input/output (IO) operations
- Dispatchers.Unconfined: This dispatcher allows your coroutine to run without being confined to a specific thread. The coroutine is typically defaulted to whatever thread is associated with the corresponding function.
The use of dispatchers ensures that threads’ performance is not unintentionally degraded by the coroutines you create.
Concessions to Reactive Programming
Kotlin coroutines are generally compatible with reactive programming. In addition to the standard coroutine functions, Kotlin coroutines have additional classes that are more specific to reactive-style programming. Two of the main examples of these classes include:
- Kotlin Flow: Kotlin Flow was created on top of Kotlin Coroutines. Where suspending functions in coroutines can only return a single value, Flow is able to return a list of values asynchronously. Flow is identical to the flowable class in RxJava.
- Kotlin Channel: Channels provide a way to transfer a stream of values between different coroutines. Channels suspend the coroutines while data is being transferred, and then allow the coroutines to continue working once the transfer is complete. The Kotlin Channel is similar to BlockingQueue in RxJava.
Kotlin coroutines, along with Kotlin Flow and Channel, have helped us make the client-server communication on our new application far more efficient for users. Whether you are making long-running calculations, processing large amounts of data, or performing IO operations, coroutines can help optimize application performance and improve user experience.