Settling the Async-Await v withContext debate in Kotlin Coroutines

Settling the Async-Await v withContext debate in Kotlin Coroutines

Ferdinand Bada's photo
Ferdinand Bada
·Jul 14, 2020·

4 min read

Subscribe to my newsletter and never miss my upcoming articles

Asynchronous programming is a new reality in programming that we (developers) have to understand. To this end, Kotlin came up with a great library in the form of Coroutines. However, there have been debates as to the patterns to use and what works and does not. One such debate is the Async-Await v withContext to perform background work. So what are these two concepts and which one should you opt for?

async-await

To understand any code concepts, it is better to dive into code examples and then see an explanation. Let us say that we have two long-running operations as shown below and we want to use the results of those tasks.

private suspend fun operationOne(): Int {
    delay(1000)
    return 20
}

private suspend fun operationTwo(): Int {
    delay(6000)
    return 35
}

The first operation will take a second while the second one will take a whole six seconds. The idea here is to show the varying amounts of time that background operations can take. If you want to use the results of those values with Coroutines, you do the following:

fun main() = runBlocking {
    val opOne = async { operationOne() }.await()
    val opTwo = async { operationTwo() }.await()
    println("The multiplied result is ${opOne * opTwo}")
}
//will print
The multiplied result is 700

Here, we are using the async block, which returns Deferred<T> that has the handy await() method. According to the documentation, this method

Awaits for completion of this value without blocking a thread and resumes when the deferred computation is complete, returning the resulting value or throwing the corresponding exception if the deferred was cancelled

Deferred is actually a type of job that we can wait on its result before doing stuff. So, once we call await(), we wait for that operation to complete. Finally, we multiply the values and print out the results. To improve the readability, you can print out information on what is happening like the following

fun main() = runBlocking {
    println("I am working")
    val opOne = async { operationOne() }.await()
    val opTwo = async { operationTwo() }.await()
    println("Done working.")
    println("The multiplied result is ${opOne * opTwo}")
}
//will print
I am working
Done working.
The multiplied result is 700

On a higher level, launch and async are the same thing. However, the former does not have a resulting value and returns a job. On the other hand, async returns Deferred<T>, which is also a type of job so it can be cancelled if needed.

Note: Async can take a context where you can choose the Dispatcher you want to use.

withContext

This will also work if you want to do long running tasks in the background and do something with the result. The computation of your code will happen line by line of course. Using our previous example, the main function will become

fun main() = runBlocking {
    println("I am working")
    val opOne = withContext(IO) { operationOneWithContext() }
    val opTwo = withContext(IO) { operationTwowithContext() }
    println("Done working.")
    println("The multiplied result is ${opOne * opTwo}")
}
//will print
I am working
Done working.
The multiplied result is 700

As you can see, the same result is printed. However, withContext needs a mandatory context to work (Dispatcher.IO in this case).

So what should you use?

If you have upgraded your Kotlin version, then this question has already been answered for you. I am not exactly sure when this was done but this post shows that it happened as early as Kotlin 1.2.50. The it, in this case, is a lint that checks whether you are using async-await and suggests a simplification. The exact message you get is

Redundant 'async' call may be reduced to 'kotlinx.coroutines.withContext'

So just use withContext to remove that annoying lint check unless you need actual concurrency and not a simple operation. In most cases, withContext will do.

Happy coding!

PS: Let us connect on GitHub , Twitter , and LinkedIn .

 
Share this