Awaiting Kotlin suspend functions in serial, parallel and lazy startup modes
Kotlin allows a thread of execution to be suspended until some other asynchronous function completes and returns a result. Suspend functions are most commonly used when a response from an external resource is needed to complete the suspend function.
Kotlin allows a thread of execution to be suspended until some other asynchronous function completes and returns a result. Suspend functions are most commonly used when a response from an external resource is needed to complete the suspend
function. Most often the external resource is a web service API or a connected network or Bluetooth device.
Creating a Suspend Function
In Kotlin a suspend function is declared with the suspend
keyword:
suspend fun doSomethingUsefulOne(): Int {
delay(100L) // pretend we are doing something useful here
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(100L) // pretend we are doing something useful here, too
return 29
}
For simplicity, these two functions use the delay
function to pause their thread for some number of milliseconds before continuing. In a production application this is where an external, blocking network request could be made.
Calling a Suspend Function in Serial
The most straightforward call to a suspend function is a simple call, which will suspend the calling thread until the suspend
function returns. In this example, one function will run at a time, e.g. doSomethingUsefulTwo
will not run until doSomethingUsefulOne
has returned.
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
suspend fun doSomethingUsefulOne(): Int {
delay(100L) // pretend we are doing something useful here
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(100L) // pretend we are doing something useful here, too
return 29
}
fun main() {
GlobalScope.launch {
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
}
}
/*
The answer is 42 Completed in 307 ms
*/
Functions called in parallel (with await to synchronize)
What if we don't want to wait for the doSomethingUsefulOne
to return before running doSomethingUsefulTwo
, but also we don't want the calling thread to continue until both functions have run? For this case, use the async
keyword before the function wrapped in a code block, and call each code block with the .await()
function.
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
suspend fun doSomethingUsefulOne(): Int {
delay(200L) // pretend we are doing something useful here
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(100L) // pretend we are doing something useful here, too
return 29
}
fun main() {
GlobalScope.launch {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
}
/*
The answer is 42 Completed in 236 ms (71ms faster than serial run)
*/
This approach is similar to the Serial approach, except it allows both suspend
functions to run a the same time on different threads. The execution is reduced from 307ms to 236ms.
Lazy Start
Building on the Parallel approach, it's possible to create the async
code blocks, but control when each async
code block starts explicitly. This could be helpful to allow doSomethingUsefulOne
to get started while doing other work to prepare for the call of doSomethingUsefulTwo
.
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
suspend fun doSomethingUsefulOne(): Int {
delay(200L) // pretend we are doing something useful here
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(100L) // pretend we are doing something useful here, too
return 29
}
fun main() {
GlobalScope.launch {
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
// some computation
one.start() // start the first function
delay(100L) // This delay has little impact on total time since doSomethingUsefulOne is the bottleneck
two.start() // start the second function
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
}
/*
The answer is 42 Completed in 243 ms
*/