Asynchronous Simplicity with Kotlin

J Banks
Software Tidbits
Published in
7 min readJun 3, 2018

Efficient Helpers

When a person is making a move from their home, they may have friends and family assist with the move. We will refer to them as moving “helpers”. Upon arrival, the helpers are standing around waiting for some work to do. The home owner begins directing helpers in various directions. One helper is directed to start packing up plates in the kitchen, another is directed to begin moving equipment out of a shed to the edge of the moving truck, and yet another helper is directed to be in the moving truck assisting with lifting items and stacking in an optimized fashion.

The idea is that all of the workers have something to do and for the most part can work separately and concurrently not waiting on the home owner or one another.

In contrast, imagine how inefficient it would be if all of the helpers were constantly yelling across the yard, “I just picked up a rake, now you can pickup a plate” in order to get work done.

This isn’t how an efficient work system operates and is why there are asynchronous and concurrent concepts in the programming world.

In this session, asynchronous processing examples are broken down while fulfilling an easy to understand set of requirements. A direct example is the need to properly identify a person then perform a series of history checks to obtain an overall approval. This can be a perfect use case for applying asynchronous and concurrent processing.

Let’s start with some specific requirements …

Requirements

  • Given a unique identifier for a person, perform a record request to obtain information about that person.
  • Once identified as a valid identity, information retrieved for the record inquiry will include work and education history.
  • Retrieval of the person record is completed in the background without impeding the main processing of the application.
  • The main processing of the application continues to accept commands even though a background request for a record is still in progress.
  • Request and status commands are handled by the application.
  • The Request command will accept a person’s unique identifier and kickoff the background record retrieval.
  • The Status command will accept a person’s unique identifier for a record request to determine if completed or in progress.
  • Once information is fully retrieved for a person, a success notification is provided back to the main processing of the application.

Design

Before constructing the solution, a sequence diagram is drafted to illustrate the primary components involved with fulfilling the requirements. A service called RecordInquiryService is identified to handle the actual back end work of obtaining all details of a record for a specific person/identity.

The RecordApplication is the entry point to handling commands from an external user. In this solution, the application will poll for commands to support the record request and status use cases.

Inquiry Service Interactions

A basic command line interface is illustrated for this solution, but the same foundational components will support a more elaborate REST-based or user interface framework. The focus for this tutorial is how the RecordInquiryService handles asynchronous work while not blocking and addressing notification of completion.

Solution

For fulfilling the requirements, Kotlin coroutines provide a straightforward way to perform these activities in the background and still allow main processing to continue to serve other commands.

The solution uses kotlinx.coroutines which is a rich library containing high-level coroutine-enabled primitives such as async, launch, etc. A coroutine is like a light-weight thread and the kotlinx.coroutines library assists in creating straightforward asynchronous logic that is easy to understand and maintain.

Although there is much more depth to coroutines, this solution uses a specific async function of coroutines along with what are called suspending functions.

Let’s break these definitions down with examples.

Coroutines

Again, a coroutine is similar to a thread but think of it as a lightweight thread. Coroutines have a life-cycle, which includes creating and starting, and it may have its execution suspended and resumed. Since it is not bound to a specific thread, its suspension and resuming may occur across threads. Also, looking at it from a thread perspective, a single thread can run many coroutines. For completion of a coroutine, it can produce a result or exception (i.e. in the form of a future).

Suspending Functions

A function marked with a suspend modifier suspends code execution without blocking the current thread of execution. It’s important to note that it can’t be called from non-suspending functions or suspending lambdas.

For example, to obtain the education history a suspending function is created.

private suspend fun getEducationHistory(request: RecordRequest):      
EducationHistory {

println("Simulating time to obtain the education history ...")
delay(5_000)
val history = EducationHistory(request.identityRecord?.identity,
"Bachelors", 2002)
request.identityRecord?.education = history
request.requiredApprovals.isEducationHistory = true

println("Done obtaining education history ...")
return history
}

Notice the delay() function, this is used for simulation of time taken to perform the education history lookup. The delay does not block the owning thread but merely delays the coroutine itself. Notice that a record request is passed in and once the education history is retrieved, it updates the request to indicate the education history has been obtained.

Here’s the corresponding suspending function that will take a bit longer (30s) in our simulation to complete the retrieval of work history.

private suspend fun getWorkHistory(request: RecordRequest): 
WorkHistory {

println("Simulating time to obtain the work history ...")
delay(30_000)
val history = WorkHistory(request.identityRecord?.identity,
"Starbucks", "Vinos Italian",
"Dan's Coffee", "Paper Delivery" )
request.identityRecord?.work = history
request.requiredApprovals.isWorkHistory = true

println("Done obtaining work history ...")
return history
}

Now, let’s look at how suspending functions are invoked using async blocks.

Async Functions

Code executed inside an Async() block is completed asynchronously and provides back a Deferred<T> return type. The Deferred<T> is a future that is non-blocking and promises to provide a result at a later time.

If wanting to use the return object, there is an await() function available which returns a Deferred result. This await() function doesn’t block the main thread of execution if invoked from that thread, it will only suspend the execution of the coroutine (think lightweight thread here) until the asynchronous operation completes.

Below is the education history suspending function that is invoked from within an async block.

async {
getEducationHistory(request)
}

In this case, the result return is not of interest as the request object is used within the suspending function for updating an approval. Again, if desired, a Deferred<T> object is available if the implementation wanted to interact with a future.

Putting these concepts together to fulfill the requirements: 1) a person identity is obtained, 2) the request is updated with the authorized identity, and steps 3) processing continues to the asynchronous blocks invoking suspending functions without blocking.

fun handleInquiry(request: RecordRequest) {    //1 - synchronous call to get identity of person
val identity: PersonIdentity = getCustomerIdentity(request)
// 2 - apply to request for history function reference
request.identityRecord = IdentityRecord(identity)
// 3 - all non-blocking to this thread of execution
async {
getEducationHistory(request)
}

async {
getWorkHistory(request)
}

async {
monitor(request)
}
}

The important thing to note is that the main thread calling the handleInquiry() function will not block on any of the async block operations. The only blocking occurs on the getCustomerIdentiy() function that is not an asynchronous operation as it needs to obtain a valid identity before retrieving any histories.

The education and work history retrieval operations are somewhat self-explanatory, but what about the monitor operation being invoked?

The monitor operation is a function written to perform a check every so often to proactively notify of a completed inquiry for this specific person/request.

private suspend fun monitor(request: RecordRequest) {    var count = 0
while(true) {
delay(5_000)
count+=5
println("Elapsed: $count seconds")
if (request.requiredApprovals.isApproved()) {
println("ALERT: All approvals are in for customer id:
${request.customerId}")

break
}
}
}

In this case, when all approvals are in, a message is printed to the console, but in a more robust solution, an event could be generated and published out for interested consumers in an asynchronous manner.

Action

Let’s run these functions as part of a sample application that polls for commands. When the ‘request’ command is issued, the user supplies a person identifier for the lookup. The application then kicks off the handleInquiry().

The output below illustrates that once the person is identified, the suspending functions are invoked in an asynchronous fashion and they kickoff their work concurrently. The polling for commands immediately continues on the main thread while the work is being done in the background. Issuing a ‘status’ command shows that the request is still in a ‘pending’ state until the monitor reports that all is complete!

Command Line View

Key Takeaways

  • The education history, work history, and monitor are suspending functions that are all kicked off and operating at the same time.
  • The command line was immediately freed up to take additional commands from the user. In this case a ‘status’ command was issued and it resulted in a status of PENDING until after the 30 seconds elapsed. Once the monitor detected full approvals, an alert was sent to the console.
  • No blocking occurred after the initial retrieval of the person’s identity before kicking off the suspending functions as part of separate asynchronous blocks.

The hope is that working with asynchronous and concurrent concepts is easier to comprehend and apply in future development and design efforts. In my opinion, the kotlinx.coroutines library makes these complexities less burdensome than previous options with other languages. As mentioned, there are many additional capabilities of Kotlin coroutines and worthy of additional research.

Until next time …

Additional References

--

--