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?
CodePudding user response:
What is the difference between the two functions?
There are 2 major differences between the 2:
- the usage is different, the
suspend
one "feels" synchronous, while thelaunch
is explicitly asynchronous - the second function breaks structured concurrency, and shouldn't be written this way
Let 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 appropriate- if you're already in the suspending world when calling this function, the existing coroutine context and jobs will be ignored
To 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.
CodePudding user response:
The shortest answer is that suspend function is a block that can be executed in CoroutineScope. So it's not the first example vs the second example. By combining those blocks you can start your own scope, and execute suspend functions using different contexts.
private suspend fun doSomething() {
withContext(Dispatchers.IO){
// task executed in io thread
}
}
private suspend fun doSomethingUI() {
withContext(Dispatchers.Main) {
// task executed in ui thread
}
}
private fun ioOperation() {
CoroutineScope(Dispatchers.IO).launch {
doSomething()
doSomethingUI()
}
}
Edit: This is just a basic sample made with simplicity in mind. It doesn't handle the proper lifecycle of the Coroutine Scope, and should not be directly used.