I've been trying to wrap my head around coroutines and I believe I have a fairly good understanding now, but there's still a couple things that aren't clear which I have questions about.
Imagine I have the below code:
lateinit var item: Int
val coroutine = globalScope.launch {
item = onePlusOne()
}
suspend fun onePlusOne(): Int {
return 1+1
}
When the suspend function onePlusOne is called, it doesn't start a new coroutine correct? It is still executing in the same one started by launch?
When the suspend function onePlusOne is called, it doesn't automatically suspend the coroutine that launch created right? If it does, how does it still execute the return 1+1 statement?
When the suspend function onePlusOne is called, it doesn't start a new coroutine correct? It is still executing in the same one started by launch?
Yes, a coroutine is only created when you create one from CoroutineScope.launch or CoroutineScope.async, withContext or runBlocking.
A suspend function does not create a new coroutine, but can suspend the caller if it calls any suspend function from inside it (it can be change of thread/dispatcher using withContext and waiting for the result, it can be a delay, or you are using suspendCancellableCoroutine to wrap a callback and resume/return the value as function return, and many more).
When the suspend function onePlusOne is called, it doesn't automatically suspend the coroutine that launch created right? If it does, how does it still execute the return 1+1 statement?
Yes it doesn't suspend it, unless you call a suspend function it is not suspended.
Suspend functions aren't magical that they suspend on their own, they are same as a non-suspend function, but they have one extra parameter appended at the time of compilation accepting a Continuation, which basically work as a callback to pause (suspend) and resume a coroutine.
Related
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:
the usage is different: the suspend one "feels" synchronous, while the launch 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:
CoroutineScopes 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.
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.
I would like to ask you why does it work?
Normally when I used collectLatest with flow my data wasn't collected on time and the return value was empty. I have to use async-await coroutines, but I have read it blocks main thread, so it is not efficient. I've made my research and find the solution using sharedflow.
Previously:
suspend fun getList: List<Items> {
CoroutineScope(Dispatchers.Main).launch {
async {
flow.collectLatest {
myItems = it
}
}.await()
}
return myItems
}
or without await-async and it returns emptyList
now:
suspend fun getList: List<Items> {
val sharedFlow = flow.conflate().shareIn(
coroutineScopeIO,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
return sharedFlow.first()
}
conflate means:
Conflates flow emissions via conflated channel and runs collector in a separate coroutine. The effect of this is that emitter is never suspended due to a slow collector, but collector always gets the most recent value emitted.
I'm not sure I understand it clearly. When I conflate flow, I just create seperate coroutine to emit what will be inside my another function as in my example shareIn().first() and using this variablesharedFlow which is suspended so will give the same effect I made asnyc-await, but in that case I do not block main thread, but only my exact *parentCoroutine-or-suspendFunction?
SharingStarted.WhileSubscribed()
It just means to start emit when subcribed.
conflate() has nothing to do with why this is working. The separate coroutine it talks about is run under the hood and you don't need to think about it. It's just to make sure your flow never causes the upstream emitter to have to wait for a slow collector, and your collector skips values if they are coming faster than it can handle them. conflate() makes it safe to have a slow collector without a buffer.
In your first code block, you are launching a new coroutine in a new CoroutineScope, so it is not a child coroutine and will not be waited for before the function returns. (Incidentally, this new coroutine will only finish when the Flow completes, and most types of Flows never complete.)
In the second code block, you are calling first() on the Flow, which suspends and gets the next value emitted by the flow and then returns that value without waiting for the Flow to complete.
Some other notes:
You should never use async { /*...*/ }.await() where await() is called immediately on the Deferred, because it is just a more convoluted version of withContext(/*...*/) { /*...*/ }.
It's a code smell to create a CoroutineScope that you never assign to a property, because the point of creating a scope is so you can manage the scope, and you obviously aren't managing it if you have no reference to it to work with.
You said you are worried about blocking the main thread, but nothing in the code you showed looks suspicious of blocking the main thread. But it's possible your flow that you are basing this on has blocking code in it. By convention it shouldn't. If that flow blocks, you should use the flowOn(Dispatchers.IO) operator on it at the source so downstream users don't have to worry about it.
Although your code worked, it doesn't make sense to create a SharedFlow in a function and immediately collect from it. It's not being shared with anything! Your code could be simplified to this equivalent code:
suspend fun getList: List<Items> {
return flow.first()
}
Started using Kotlin coroutines recently
here is the syntax:
main(){
launch(Dispatchers.Main){
delay(2000)
print("inside coroutine")
}
print("outside coroutine")
}
I understand that outside coroutine is printed first and then inside coroutine is printed because delay is a suspend function and it blocks the coroutine only and not the thread itself.
But as the coroutine would need to be executed somewhere else(like on a different thread) for it to know when to resume, how is this taken care of?
It can be any process intense suspend function instead of delay.
Only thing I can't understand is Does the suspend function actually run on a different thread under the hood irrespective of the Dispatcher provided in launch{} builder?
suspend functions are designed to block current coroutine, not the thread, it means they should run in background thread. For example delay function blocks coroutine for a given time without blocking a thread and resumes it after a specified time. You can create a suspend function which runs on background thread like the following:
suspend fun someSuspendFun() = withContext(Dispatchers.IO) {
// do some long running operation
}
Using withContext(Dispatchers.IO) we switch the context of function execution to background thread. withContext function also can return some result.
To call that function we can use a coroutine builder:
someScope.launch(Dispatchers.Main){
someSuspendFun() // suspends current coroutine without blocking the Main Thread
print("inside coroutine") // this line will be executed in the Main Thread after `someSuspendFun()` function finish execution.
}
delay() function is run asynchronously under the hood. Other suspend functions would block the thread if run using Dispatchers.Main
This question already has answers here:
Suspend function 'callGetApi' should be called only from a coroutine or another suspend function
(3 answers)
Closed 2 years ago.
I am caching my users purchases locally using Room. For this, I created an insert function, which is suspending.
First, in my repository, I called the insert function from a function called launchBillingFlow which is suspending and its signature looks like this:
suspend fun launchBillingFlow(activity, skuDetails)
I have had no errors doing it like this. Afterwards, I created a private function for acknowledging the purchase and moved the insert call to there. It's signature looks like this:
private suspend fun acknowledgePurchase(purchase)
But when I call the insert function from there, I get following error:
Suspension functions can be called only within coroutine body
And I don't understand why this is. I call launchBillingFlow from a coroutine body and it calls acknowledgePurchase. And I am doing a similar thing with querying the SkuDetails, too. Is this a lint bug or am I missing something?
It is not a lint bug.
It says you need coroutine context in your method
The lint message is:
suspend function can only be called within coroutine body
I know that suspend function can only be called inside another suspend function and inside coroutine.
In contrast this message says it can only be called inside coroutine but In fact, this is not a contradiction.
To understand the message you can ignore suspend word in it and read the message like this:
The [function name] can only be called within coroutine body because it needs coroutine context.
The reason it needs coroutine context is not because it is suspend it is because inside it you have called a method that needs context.
I went through the Kotlin Coroutine, I understood how it works but I have a confusion between Kotlin coroutine & Android Async.execute() & Async await. The Kotlin coroutine runs in the background and does not block on the UI thread but the same thing happens when we start android AsyncTask(with the methods doInBackground onPostExecute and onProgressUpdate overridden), it also does the computation in a background thread and publishes the result on the UI thread.
Async-await returns a Deffered object means the result will obviously going to be returned in future.
Can Anyone Explain what's the difference between these.
Let's try to break this down:
The Kotlin coroutine runs in the background
A coroutine CAN run in the background
does not block on the UI thread
Let's talk about what a coroutine is:
A coroutine can be thought of as the code that gets passed to one of the coroutine builder functions i.e. launch {}
By definition when a coroutine is launched, suspend functions within it do not block the corresponding Thread when they are reached; they "pause" the coroutine.
When the suspension point is reached, it is as if you were telling the code to "call you back later" when the result is available; a suspension point can be though of as a callback.
Let's look at an example:
fun main() {
val job = MainScope().launch {
doSomeWork()
}
suspend fun doSomeWork() {/*expensive work goes here*/}
}
When doSomeWork() is reached the code will suspend the coroutine, i.e. the suspend modifier is indicating to the coroutine framework that it can go do some other coroutine related work and then come back to this point when doSomeWork() is done.
Since this coroutine is launched using MainScope() it will be launched in the main Thread. That is why I said that coroutines CAN run in a background Thread but not always do so. In this case it does not, but it still does not block the UI Thread.
In the other hand, AsyncTask was (it is deprecated as of API 30) a mechanism that performed a tasks in a background Thread and posted the result back to the UI Thread
For the difference between CoroutineScope.async{} and CoroutineScope.launch{} we can look at the return values for each. As I showed in the example. launch{} returns a Job which is a representation of the lifecycle of the coroutine itself. Using the Job you can cancel() or join() the coroutine; you have control over its lifecycle. As you mention, async{} returns a Deffered<T> which is a representation of a future value. When await() is called on the Deffered<T> the coroutine is suspended until the result is ready to be consumed.