Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to run several Kotlin coroutines in parallel and wait for them to complete before proceeding

I need to run 2 coroutines in parallel and wait for them to finish before proceeding. The code below works but it uses GlobalScope which is not the best way to do it.

Is there a better way?

fun getInfo(onSuccess: () -> Unit, onError: () -> Unit) {
        GlobalScope.launch(Dispatchers.IO) {
            try {
                coroutineScope {
                    launch { getOne() }
                    launch { getTwo() }
                }
                onSuccess.invoke()
            } catch (e: Throwable) {
                onError.invoke()
            }
        }
    }
like image 926
alexbtr Avatar asked Apr 17 '20 01:04

alexbtr


People also ask

How do you wait for coroutine to finish Kotlin?

We can wait for the coroutine to finish by calling join() on the Job. For example, suppose we have a suspend function to download some files. We can launch this coroutine and capture the resulting job, which we can later use to join — to wait for the operation to complete.

Do Kotlin coroutines run in parallel?

Kotlin's Coroutines enabling you to write parallel code easily, in a sequential way, and without worrying about the contextual overheads you know from using threads in Java.

Do coroutines run concurrently?

Coroutines can be executed concurrently using a multi-threaded dispatcher like the Dispatchers.

How do you achieve concurrency in Kotlin?

We’re going to use coroutines instead of threads as the promoted way to achieve concurrency in Kotlin. Kotlin’s async function allows running concurrent coroutines and returns a Deferred<T> result. Deferred is a non-blocking cancellable future to act as a proxy for a result that is initially unknown.

Can We do background tasks in parallel using Kotlin coroutines?

Similarly, we can do any type of background tasks in parallel using Kotlin Coroutines. Let's take another example. Join and learn Dagger, Kotlin, RxJava, MVVM, Architecture Components, Coroutines, Unit Testing and much more.

How do I make two networks in parallel in Kotlin?

The ParallelNetworkCallsViewModel, then asks the data layer for the list of users using the ApiHelper. The ViewModel makes the two networks in parallel which are as getUsers and getMoreUsers. As you can see below, the ViewModel uses the Kotlin Coroutines and LiveData.

How are Kotlin coroutines managed?

Kotlin coroutines are not managed by the operating system; they are a language feature. The operating system doesn’t have to worry about coroutines or planning them. Coroutines handle all these tasks by themselves using cooperative multitasking.


3 Answers

I would suggest implementing getInfo as a suspend function which knows the context it is supposed to run on. This way it does not matter from which context you invoke it (*).

Additionally, I would not use callbacks to proceed afterwards. You can just decide from what getInfo() returns on how to proceed (**).

This is actually the greatest thing about coroutines, you can turn callback based code in code that reads like sequential code.

Since you don't care for the result of getOne() and getTwo(), using launch is the correct way. It returns a Job. You can suspend the coroutine until both functions are finished with joinAll() which can be invoked on Collection<Job>.

suspend fun getInfo() = withContext(Dispatchers.IO) {
    try {
        listOf(
            launch { getOne() },
            launch { getTwo() }
        ).joinAll()
        false
    } catch (e: Throwable) {
        true
    }
}

You don't need to use GlobalScope, just create your own (***).

I used Default as context to launch getInfo, but any other context would be fine too, since getInfo will run on the one it is supposed to.

val newScope = CoroutineScope(Dispatchers.Default).launch {
    val error = getInfo()
    if(error) {
        onSuccess()
    } else {
        onError()
    }
}
// "newScope" can be cancelled any time

* In case I used Dispatcher.IO to pretend that the two functions are doing some long running IO work.

** I used a simple boolean here, but of course you can return something more meaningful.

*** or hook into some scope given by the sourrouding framework which is lifecycle-aware

like image 94
Willi Mentzel Avatar answered Oct 22 '22 09:10

Willi Mentzel


You can use any scope you like. That doesn't affect your question. GlobalScope is discouraged because it doesn't encapsulate your tasks.

Which scope to actually use to launch your coroutine is going to depend entirely on the context of what you're doing and what strategy of encapsulation you're using.

To launch multiple coroutines at once and wait for all of them, you use async and await(). You can use awaitAll() on a list of them if you simply need all of them to finish before continuing.

It seems odd that you use the IO dispatcher to run your callbacks on, but I'll leave that because I'm blind to the context of your code.

fun getInfo(onSuccess: () -> Unit, onError: () -> Unit) {
        GlobalScope.launch(Dispatchers.IO) {
            try {
                coroutineScope {
                    listOf(
                        async { getOne() }
                        async { getTwo() }
                    }.awaitAll()
                }
                onSuccess.invoke()
            } catch (e: Throwable) {
                onError.invoke()
            }
        }
    }
like image 39
Tenfour04 Avatar answered Oct 22 '22 10:10

Tenfour04


You can create a CoroutineScope inside the class you're working on and build coroutines just by calling launch builder. Something like this:

class MyClass: CoroutineScope by CoroutineScope(Dispatchers.IO) { // or [by MainScope()]

    fun getInfo(onSuccess: () -> Unit, onError: () -> Unit) {
        launch {
            try {
                val jobA = launch { getOne() }
                val jobB = launch { getTwo() }
                joinAll(jobA, jobB)
                onSuccess.invoke()
            } catch (e: Throwable) {
                onError.invoke()
            }
        }
    }

    fun clear() { // Call this method properly
        this.cancel()
    }

}

Make sure to cancel any coroutine running inside the scope by calling cancel properly.

Or if you're working inside a ViewModel just use the viewModelScope instead.

like image 30
Glenn Sandoval Avatar answered Oct 22 '22 10:10

Glenn Sandoval