Parallel suspending functions

Parallel suspending functions

Running code in the non-main thread in Kotlin is quite easy using coroutines.

You just define the coroutine scope and then call all your suspend functions. This is super convenient and easy to reason because it's run sequentially by default. That means that each suspend function waits for completion before the next one executes.

suspend fun callService1(): Int {
    return withContext(Dispatchers.IO) {
        [...]
    }
}

suspend fun callService2(): Int {
    return withContext(Dispatchers.IO) {
    	[...]
    }
}

class ViewModel {
   [...]

    fun onInit() {
       	viewModelScope.launch {
            liveData1.value = callService1() 
            liveData2.value = callService2()
    	}
    }
}

In the above example, Service 1 will be called, completed and only then Service 2 will be called. But what if the 2 services are completely independent and you are just wasting time executing them sequentially?

The first step is to convert the service calls into Deferred<T> to be able to control when to start the async work. Then use awaitAll to start the async work in parallel.

class ViewModel {
   [...]

    fun onInit() {
       	viewModelScope.launch {
            awaitAll(
               async {
                liveData1.value = callService1() 
               }, 
               async {
                liveData2.value = callService2()
               })
    	}
    }
}

Although this would work, using async without returning any result is not a best practice. Another, more concise, way to run those in parallel is to use multiple launch for the same coroutine scope.

class ViewModel {
   [...]

    fun onInit() {
       	viewModelScope.launch {
            launch {
                liveData1.value = callService1()
            }
            liveData2.value = callService2() 
        }
    }
}

In case you have a grouping object for the async values, you can use await() on the Deferred to get those values on demand. Note that ordering matters: the async calls must come before the await calls for this to be executed in parallel (that's why the intermediate vals s1 and s2).

class ViewModel {
   [...]

    fun onInit() {
       	viewModelScope.launch {
            val s1 = async { callService1() }
            val s2 = async { callService2() }
            liveDataCombined.value = CombinedData(
                s1.await(),
                s2.await()
            )
    	}
    }
}

Hopefully, this will make your apps a bit faster with no significant refactoring!

Show Comments