I already have 2 long running task, each of them wrapped inside withContext. But now I want to run them parallel and wait for the result of them. So I wonder if I use them like below(wrapped them with async):
val result1 = async {
withContext(Dispatcher.IO) {
someLongRunningBackgroundWork1...
....
}
}
val 2 = withContext(Dispatcher.IO) {
someLongRunningBackgroundWork2...
....
}
}
This works fine. But I wanted to understand if there is any trade offs behind the scene?
CodePudding user response:
async(Dispatchers.IO) { /*...*/ }
is identical logic as async { withContext(Dispatchers.IO) { /*...*/} }
. In either case, uncaught exceptions will cause async
to fail. If you use a bare async
call like that inside a coroutine, it is a child coroutine and the exception will be propagated to the parent. If async
is called on a specific CoroutineScope, it is not a child coroutine and will not propagate its exception unless await()
or join()
is called on it. Typically, when you want to run some parallel tasks in a coroutine and then continue, you wrap them together in a coroutineScope
call.
You could do something like this in your coroutine to break down the parallel work. Any uncaught exception thrown inside coroutineScope
is rethrown outside it, and will cancel any still-running children coroutines inside coroutineScope
.
val (result1, result2) = coroutineScope {
val result1 = async(Dispatcher.IO) {
//someLongRunningBackgroundWork1...
//....
}
val result2 = withContext(Dispatcher.IO) {
//someLongRunningBackgroundWork2...
//....
}
result1.await() to result2
}
The advantage over what you have now is that if either of the code blocks fails, the other parallel tasks will automatically be cancelled.
CodePudding user response:
As you say in the comment, your functions use withContext(IO)
and you don't want to change that. Wrapping them in async
will first start the coroutine in the current dispatcher (if you're on Android, that would be Main
) and then immediately switch to IO
. While it won't cost you a lot, it's still unnecessary. If you instead wrap the functions in async(IO)
, the coroutine will start directly on the correct dispatcher and the inner withContext(IO) { }
will be basically a no-op.
As Tenfour04 brings up, launching off background work and awaiting on its result should be encompassed by a single unit of work, which should immediately fail as soon as any part of it fails. You don't show where you call await()
on your launched coroutine, but as a rule both the async
and await
statements should occur within the same coroutineScope
block. When that block is over, you have the guarantee that no ongoing work is left dangling in the background, and you also save time and resources by cancelling it if any other concurrent subtask (or the main task) fails.