https://kotlinlang.org logo
Title
t

Tim Malseed

05/30/2021, 12:34 PM
I have a suspend function that looks something like:
suspend fun foo() {
    collection.forEach { element -> 
        withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
           // Some long running task processing 'element'
        }
    }
}
I'm wondering what would be the simplest way to parallelize the long running tasks (each element would be processed in parallel) I think this is what
async
might be for? It's just not something I've used so far, and I'm not sure what the syntax is..
d

diesieben07

05/30/2021, 12:37 PM
Yes, you can use `async`:
suspend fun foo(list: List<String>) {
    withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
        coroutineScope {
            val futures = list.map {
                async { it.uppercase() /* heavy computation here */ }
            }
            val results = futures.awaitAll()
        }
    }
}
t

Tim Malseed

05/30/2021, 12:41 PM
Thanks. It looks like
suspend fun foo(collection: List<Bar>) {
    collection.map { element -> 
        withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
            async {
               // Some long running task processing 'element'
            }
        }
    }.awaitAll()
}
Might be sufficient (no need to use an additional scope?)
d

diesieben07

05/30/2021, 12:51 PM
ah yes of course,
withContext
has coroutineScope as well.
t

Tim Malseed

05/30/2021, 1:34 PM
The actual implementation is a bit more complicated, so perhaps there's a mistake, but it seems this code still executes sequentially
d

diesieben07

05/30/2021, 1:40 PM
awaitAll should definitely make it so it runs concurrently
t

Tim Malseed

05/30/2021, 1:42 PM
I wonder if it's because I have a few more
withContext{}
inside the async (I'm trying to get back on the main thread to post messages to the UI)
d

diesieben07

05/30/2021, 1:43 PM
Well the main thread is just that, one thread, so it will do its parts synchronously
t

Tim Malseed

05/30/2021, 1:44 PM
suspend fun foo(collection: List<Bar>) {
    collection.map { element -> 
        withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
            async {
               print("start of iteration ($element)")
               doSomeStuff()
               withContext(Dispatchers.Main) {
                   listener.notify()
               }
               doMoreStuff()
               print("end of iteration ($element)")
            }
        }
    }.awaitAll()
}
This prints something like "start of iteration (element 1)" "end of iteration (element 1)" "start of iteration (element 2)" "end of iteration (element 2)"
Maybe because
withContext()
 is a suspension point?
d

diesieben07

05/30/2021, 1:49 PM
Yeah I think you want
async(<http://Dispatchers.IO|Dispatchers.IO>)
instead
t

Tim Malseed

05/30/2021, 1:51 PM
Oh man, this gets confusing
The code actually is more like..
suspend fun foo(collection: List<Bar>) {
    collection.map { element -> 
        withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
            async {
               print("start of iteration ($element)")
               doSomeStuff()
               withContext(Dispatchers.Main) {
                   listener.notify()
               }
               doMoreStuff()
               withContext(Dispatchers.Main) {
                   listener.notify()
               }
               doMoreStuff()
               withContext(Dispatchers.Main) {
                   listener.notify()
               }
               print("end of iteration ($element)")
            }
        }
    }.awaitAll()
}
If those are
async
instead of
withContext()
, then when do I call
await
on them?
The idea is I'm trying to report progress back to the UI, while the function continues to do its work
So
listener.notify()
needs to be called at the right time
Oh, I think I see the mistake
d

diesieben07

05/30/2021, 1:55 PM
I think this is more akin to what you need:
suspend fun foo(collection: List<Bar>) {
    coroutineScope {
        collection.map { element ->
            async(<http://Dispatchers.IO|Dispatchers.IO>) {
                print("start of iteration ($element)")
                doSomeStuff()
                withContext(Dispatchers.Main) {
                }
                doMoreStuff()
                withContext(Dispatchers.Main) {
                }
                doMoreStuff()
                withContext(Dispatchers.Main) {
                }
                print("end of iteration ($element)")
            }
        }.awaitAll()
    }
}
👍 1
t

Tim Malseed

05/30/2021, 1:56 PM
suspend fun foo(collection: List<Bar>) {
    collection.map { element -> 
        withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
            async {
Should be
suspend fun foo(collection: List<Bar>) {
    withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
        collection.map { element -> 
            async {
Thanks for your help
z

Zach Klippenstein (he/him) [MOD]

05/30/2021, 2:42 PM
If you don’t need results from the coroutines, use
launch
instead of
async
and
join
instead of
await
.
👍 2
t

Tim Malseed

05/30/2021, 2:43 PM
Ok, thanks