Do not understand the use of CancelAndJoin() in following code in kotlin - android

I recently came across this code and do not understand the use of cancelAndJoin(). As I understand it, the extension function cancels the job and wait on its completion. So this coroutine would cancel itself and would never get any work done. But as it does my understanding must be wrong. Could someone explain this use of cancelAndJoin()?
job = viewModelScope.launch {
job?.cancelAndJoin()
while (isActive) {
//do some work
delay(1000L)
}
}

As written, the code has a race condition. It looks like it's trying to cancel the previous job, but it is possible job may already refer to itself by the time it executes that line of code.
If we copy the reference ensure we are cancelling the old job, it makes more sense:
val previousJob = job
job = viewModelScope.launch {
previousJob?.cancelAndJoin()
while (isActive) {
//do some work
delay(1000L)
}
}
It is cancelling the previous Job, and waiting to make sure it has reached a point of cancellation and actually exited, before the new coroutine continues with its work.
The reason to use cancelAndJoin() instead of just cancel() is if there are a series of non-cancellable function calls that you must wait for to avoid some other race condition.
Since delay() cooperates with cancellation, the isActive can be replaced with true.

Related

CoroutineScope vs suspend func

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.

How this change from runBlocking to sharedFlow work?

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()
}

Android Background Job Service on CoroutineScope and Dispatcher.IO

Context
I'm trying to update a couple of background Job in Android 6. The main goal is to make the work done by the background job light and non ui thread blocking. So I decided to some things:
GlobalScope may be a bad idea since a dying thread will terminate the others. So CoroutineScope will to the trick
Since I need I/O and Network stuff done, I need a thread pool designed for those tasks. So I'll use Dispatchers.IO
The work of the bound JobServices are designed for "fire and forget". The app doesn't care - so I'll just launch the job instead of waiting for the output of an async
Code
All together I designed the following JobService class as a template for others to be migrated or implemented. Since the job is shedulded the class will return false to the inherited functions.
import android.app.job.JobParameters
import android.app.job.JobService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class TestJobService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
CoroutineScope(Dispatchers.IO).launch {
// Fire and Forget
}
return false
}
override fun onStopJob(params: JobParameters?): Boolean {
return false
}
}
Questions
If you want to answer please use the number of the question.
Is CoroutineScope a good idea?
I'm getting errors from the main thread "I/art: Note: end time exceeds epoch:" - this is confusing and could indicate that the JobService does to much work on the UI thread. But I have no errors from the choreographer which should normally be the case if the UI is in trouble.
Is it a bad idea to return false in the JobService functions
Currently used Links
https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb
https://medium.com/androiddevelopers/coroutines-on-android-part-ii-getting-started-3bff117176dd
https://medium.com/androiddevelopers/coroutines-on-android-part-iii-real-work-2ba8a2ec2f45
https://developer.android.com/kotlin/coroutines/coroutines-adv?hl=de#coroutinescope
https://kotlinlang.org/docs/coroutines-basics.html#extract-function-refactoring
The only thing your usage of CoroutineScope does is provide the IO dispatcher. As it stands, if any of the work done in the coroutine throws an exception, the whole scope will be cancelled. That may be a problem if you are launching multiple coroutines from within it. If you do care about that you would also need to set it up with a SupervisorJob: CoroutineScope(Dispatchers.IO) + SupervisorJob().
It's hard to identify the meaning of that error without your actual code. From researching quickly, if you do something that should only occur on the main thread (like accessing certain Android components perhaps), that may cause that error. Perhaps inspect your code for those occurances and wrap it in a:
withContext(Dispatchers.Main) { ... }
But again this is just a guess without having any actual code.
The docs indicate for onStartJob:
The system holds a wakelock on behalf of your app as long as your job is executing. This wakelock is acquired before this method is invoked, and is not released until either you call jobFinished(android.app.job.JobParameters, boolean), or after the system invokes onStopJob(android.app.job.JobParameters) to notify your job that it is being shut down prematurely.
Returning false from this method means your job is already finished. The system's wakelock for the job will be released, and onStopJob(android.app.job.JobParameters) will not be invoked.
And according to the wakelock docs,
A wake lock is a mechanism to indicate that your application needs to have the device stay on.
So no, it does not seem correct to return false from onStartJob as the JobService will not properly maintain the wakelock. To remedy this, you should return true from this method, and upon completion of the coroutine, you should call jobFinished to indicate the job has completed all of its work.
For onStopJob, the return values:
true to indicate to the JobManager whether you'd like to reschedule this job based on the retry criteria provided at job creation-time; or false to end the job entirely. Regardless of the value returned, your job must stop executing.
For this case it's hard to tell what is the correct value based on your snippet. You should be able to figure out which is correct based on your use case. In this method you should be cancelling your previously created CoroutineScope. You will need to maintain a reference to the scope in order to do this.

Cancelling custom CoroutineScope

val scope = CoroutineScope(
Job() + Dispatchers.Main
)
scope.launch {
beforeExecute()
val result = withContext(dispatcher) { doInBackground(*params) }
if (!isCancelled) {
postExecute(result)
} else {
cancelled(result)
}
status = Status.FINISHED
}
scope.cancel()
If i put scope.cancel() outside launch it cancels the coroutine immediately without calling launch block code.Is this expected?Why it happens?Should cancel be placed inside launch at end of launch block if i want coroutine to end once it finish executing code inside launch?
Update
As per Hau Luu's answer and Marko Topolnik's comment,
”at the end of launch, I think the task is done and you don't need to
manually cancel the Coroutine.”
and
“Once your task is done, the coroutine disappears from memory.”
But here in Case 2 ,if I start another launch it is executed unless we cancel the coroutine inside first launch as in Case 1.
So is there any surety that after task is completed the coroutine disappears from memory without us manually calling cancel() ?Bcoz compiler will never know which is the last launch that is going to execute after which it needs to end coroutine
Case 1
scope.launch {
Log.e("Task","1");
scope.cancel()
}
scope.launch {
Log.e("Task","2");
}
Only Task 1 is printed
Case 2
scope.launch {
Log.e("Task","1");
}
scope.launch {
Log.e("Task","2");
}
Both Task 1 and 2 are printed
Your code can be translated to natural language as "Cancel the given coroutine right after scope.launch is executed" so I think this is expected behavior.
And for the other question, we only want to cancel a coroutine when there is something wrong during the execution process - hey coroutine, during the execution of the task I gave you. if there is sth wrong happen. Kill yourself. So at the end of launch, I think the task is done and you don't need to manually cancel the Coroutine.
Update: I write this as an answer because I can't write code in comment.
CoroutineScope was designed to react to the lifecycle of the object that create/start/house a coroutine. So when you call the cancel method on a CoroutineScope, you're stoping everything. Stoping not canceling. All child coroutines that were created by the scope, all jobs they are executing, cancel them all, and nothing more. The job is done. That's is why you can't start another launch after scope.cancel
A CoroutineScope will create and hold refs to a bunch of Corrountine via builder methods like launch and async. When you want to cancel a specific Coroutine. You need to cancel the Job that returned by the builder. Not cancel the scope that is housing them.
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
val job1 = scope.launch{ print('Task 1') }
job1.cancel()
val job2 = scope.launch{ print('Task 2') }
Task 2 will be printed as normal.

withContext after a CoroutineScope.launch - will CoroutineScope.launch block?

I just stumbled over this code:
fun addHeaderAndSubmitList(list: List<SleepNight>?) {
adapterScope.launch {
val items = when (list) {
null -> listOf(DataItem.Header)
else -> listOf(DataItem.Header) + list.map { DataItem.SleepNightItem(it) }
}
// isn't there any code required to wait for the
// adapterScope.launch coroutine to finish?
withContext(Dispatchers.Main) {
submitList(items)
}
}
}
found in this file of the google sleeptracker example.
I already added my question as comment in the code example. I am new to coroutines but to my knowledge adapterScope.launch is non-blocking, so adapterScope.launch might not be finished until
withContext(Dispatchers.Main) {
submitList(items)
}
is reached? Am I wrong about this? If not, how to fix it?
See launch.
Launches a new coroutine without blocking the current thread [...]
Here's what happens:
addHeaderAndSubmitList uses launch to start some asynchronous work. The work will finish naturally or will be terminated when adapterScope's lifecycle ends. Meanwhile addHeaderAndSubmitList finishes immediately.
Whatever is inside launch {} runs sequentially. submitList(items) is called after val items = .... Each happens effectively on a different thread, but the order is guaranteed.
The code inside launch { } runs sequentially in a blocking fashion inside the adapterScope, meaning all the code above the withContext(Main) is run and finished before switching contexts to submit the list to the adapter on the main thread.
The entire code block is likely running in an Default or IO context, so it runs in a blocking fashion outside the main thread, until it reaches the withContext(Main) to post the results to the main thread.

Categories

Resources