Home > Mobile >  Difference between GlobalScope and runBlocking when waiting for multiple async
Difference between GlobalScope and runBlocking when waiting for multiple async

Time:12-19

I have a Kotlin Backend/server API using Ktor, and inside a certain endpoint's service logic I need to concurrently get details for a list of ids and then return it all to the client with the 200 response.

The way I wanted to do it is by using async{} and awaitAll()

However, I can't understand whether I should use runBlocking or GlobalScope. What is really the difference here?

fun getDetails(): List<Detail> {
    val fetched: MutableList<Details> = mutableListOf()

    GlobalScope.launch { --> Option 1
    runBlocking { ---> Option 2
    Dispatchers.IO --> Option 3 (or any other dispatcher ..)

         myIds.map { id ->
             async {
                 val providerDetails = getDetails(id)
                 fetched  = providerDetails
             }
         }.awaitAll()
     }
     return fetched
}

CodePudding user response:

launch starts a coroutine that runs in parallel with your current code, so fetched would still be empty by the time your getDetails() function returns. The coroutine will continue running and mutating the List that you have passed out of the function while the code that retrieved the list already has the reference back and will be using it, so there's a pretty good chance of triggering a ConcurrentModificationException. Basically, this is not a viable solution at all.

runBlocking runs a coroutine while blocking the thread that called it. The coroutine will be completely finished before the return fetched line, so this will work if you are OK with blocking the calling thread.

Specifying a Dispatcher isn't an alternative to launch or runBlocking. It is an argument that you can add to either to determine the thread pool used for the coroutine and its children. Since you are doing IO and parallel work, you should probably be using runBlocking(Dispatchers.IO).

Your code can be simplified to avoid the extra, unnecessary mutable list:

fun getDetails(): List<Detail> = runBlocking(Dispatchers.IO) {
    myIds.map { id ->
        async {
             getDetails(id)
        }
    }.awaitAll()
}

Note that this function will rethrow any exceptions thrown by getDetails().


If your project uses coroutines more generally, you probably have higher level coroutines running, in which case this should probably be a suspend function (non-blocking) instead:

suspend fun getDetails(): List<Detail> = withContext(Dispatchers.IO) {
    myIds.map { id ->
        async {
             getDetails(id)
        }
    }.awaitAll()
}
  • Related