I'm a little confused. I know that if a function wants to work with coroutines, it should be declared as suspend For example:
private suspend fun doSomething() {
withContext(Dispatchers.IO) {
//do something
} }
And I also know that there is such a way to use coroutines without the function being suspend. like:
private fun doSomething1() {
CoroutineScope(Dispatchers.IO).launch {
//do something
} }
What is the difference between the two functions? When to use the first example and when to use the second example?
What is the difference between the two functions?
There are 2 major differences between the 2:
suspend
one "feels" synchronous, while the launch
is explicitly asynchronousLet me elaborate.
The suspend
function appears synchronous from the usage perspective: when you call it, the next line of code is only executed when the function is done (like with any other regular function). This makes it easy to reason about. You can even assign the return value of a suspend
function to a variable, and go on with your life as if the function wasn't suspend
. That is, when you're in a suspend
context already of course. When you're not, you have to start the "root" coroutine with an explicit coroutine builder (like launch
, async
or runBlocking
).
When using launch
, you're explicitly starting an asynchronous task, and thus the code after launch
runs concurrently with what's inside the launch
. So in turn, when calling doSomething1()
, the code after it will run concurrently with whatever is in the launch
inside. However, it is really not clear from the API's perspective that this function will launch a task that outlives it. This also goes with the fact that you shouldn't create "free" coroutine scopes like this. I'll elaborate below.
When to use the first example and when to use the second example?
Use suspend
functions as much as possible to keep things simple. Most of the time, you don't need to start tasks that outlive the function call, so this is perfectly fine. You can still do some work concurrently inside your suspend function by using coroutineScope { ... }
to launch some coroutines. This doesn't require an externally-provided scope, and all the computation will happen within the suspend function call from the caller's perspective, because coroutineScope {}
will wait for the child coroutines to complete before it returns.
The function using launch
as written here is very poorly behaved, you should never write things like this:
CoroutineScope
s should not be created on the spot and left for dead. You should keep a handle on it and cancel it when appropriateTo avoid these problems, you can make the API explicit by making the CoroutineScope
a receiver instead of creating one on the spot:
private fun CoroutineScope.doSomething1() {
launch(Dispatchers.IO) {
//do something
}
}
But only use this approach if the essence of the function is to start something that will keep going after the function returns.