I want to implement a retry with a delay in a fragment, so I thought it would be a good idea to use the viewModelScope like so:
myViewModel.viewModelScope.launch {
delay(20000)
doSomething()
}
What's the point of doing this if doSomething() will still run despite the app went to the background before delay finished?
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 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.
I have a video playing application. I have implemented Vertical Viewpager2 with fragment. The fragment contains fullscreen exoplayer where I load the video and have a button to open the profile of the user who have posted the video. I make all the API calls in viewmodel as
viewmodelScope.launch(Dispatchers.IO){
... //making api call and updating the livedata
...
}
I am also using
lifecycleScope.launch(Dispatchers.IO){
... //start caching the video
}
The Problem is :
When I scroll like 40 - 45+ items and launch any coroutine on Dispatchers.IO, the launch block is never executed hence the api calls are never made and app stops working.
Debugging results :
I tried to debug why the launch block is not executing with debug break points. In the LimitingDispatcher class, there is variable called inFlightTasks which is used to schedule the task if the parallelism limit is reached (which is 64 by default). So, my current task is added to ConcurrentLinkedQueue<Runnable>. I waited for an hour but after scheduling the task it never resumed the task again. I checked the queue for the tasks and at the time of testing it had around 150-170 tasks but none of them resumed.
Any idea how to solve this problem.
Note : I cannot remove the caching of video(which is am using in onStart() of the fragment).
EDIT
I tried with lifecycleScope.launch{ } and withContext(Dispatchers.IO){ }, but I am still getting the same issue.
In viewModelScope
viewModelScope.launch(Dispatchers.IO){
val response = repository.call()
when(repository){
is Resource.Sucess -> livedata.postValue(response.data)
is Resource.Error -> livedata.postValue(response.error)
}
}
In lifecycleScope
lifecycleScope.launch(Dispatcher.IO){
try{
startVideoCaching()
}catch(e : Exception){
e.printStackTrace()
}
}
And both the functions in launch are purely suspend functions.
Edit 2
I am adding the screen shots of my functions and the Dispatchers.IO's queue data.
Debug sscreenshot.
The functions named in the debug screenshot.
In the sendAnalytics I am using lifecycleScope only
cacheVideo() is called from onStart().
markVideoView() is called from onResume()
I tried with yield() but still no change in the behavior.
I am using all this methods in Fragment which is a part of ViewPager2 items.
If it reached the parallelism limit, and the other ones are put in the queue, the only
logical reason they're not resuming and executing is that those previously called
are not suspending/returning to free up the threads for the queued ones to run.
What you should do is make your functions cancellable (cooperatively cancellable using
isActive) and those coroutines that are fired for the items that are no longer visible
on the screen (scrolled off the screen) should be cancelled, since there is no point of
calculating something not visible on the screen. By cancelling those coroutines you
should always have enough free threads to run those coroutines for the items currently visible on the screen.
In case you don't want to cancel coroutines fired for the items not currently on the screen, you can call yield() inside your functions to deliberately yield thread to other coroutines that run on the same thread pool.
I've looked at plenty of articles online but I'm still a bit confused as to what happens specifically, step by step, when suspend functions are suspended in coroutines.
I know that a suspend function, under the hood, is just a regular function with a Continuation parameter that allows it to resume, but my confusion is regarding where that suspend function or coroutine goes and where it comes back once resumed.
I've heard a few people saying "they don't necessarily come back to the same thread" and I don't get it, can someone please explain this to me step by step?
TLDR;
There is no guarantee, it may or may not,
it really depends that on the following points:
Is the dispatcher is multi-threaded?
Is there any dispatcher override in between?
LONG Answer
A coroutine has a CoroutineContext that specify how it behaves, where it run.
A CoroutineContext is mainly build up with four elements: Job, CoroutineName, CoroutineExceptionHandler and Dispatcher.
Its responsibility of the dispatcher to dispatch the coroutine. A dispatcher can be paused to stop coroutines to even run (this is useful in unit testing) mentioned here in the android conference talk, it may be a single-threaded dispatcher just like Dispatchers.Main, it has an event-loop like javascript has.
So, it really depends that on the following points:
Is the dispatcher is multi-threaded?
For example: This will run on single thread.
suspend fun main() {
val dispatcherScope = CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher())
val job = dispatcherScope.launch {
repeat(10) {
launch {
println("I'm working in thread ${Thread.currentThread().name}")
// every coroutine on same thread
}
}
}
job.join()
}
Run it here, other single threaded dispatchers: Dispatchers.Main
Is there any dispatcher override in between?
With the same context, if we override the dispatcher before launch, it will change the thread even if original context is based on single-threaded event-loop, each coroutine will run on different thread creating 10 different thread:
dispatcherScope.launch {
repeat(10) {
launch(Dispatchers.IO) {
println("I'm working in thread ${Thread.currentThread().name}")
// every coroutine on same thread
}
}
}
Run it here, other multi-threaded Dispatchers: Dispatchers.Default, Executor based dispatcher, Dispatchers.Unconfined (this launch coroutine in any free thread).
The short answer is: they execute somewhere and come back with the result to somewhere.
The long(er) explanation for the short answer:
"Somewhere" might be the same thread, it might be a different thread - it depends on the dispatcher and in many cases, the current state of the dispatcher. For instance, the contents of a SequenceScope will (by default) run on the same thread.
Another case where a suspend function might run on the same thread is if it's using the same dispatcher as the calling function.
Dispatchers can also share threads between them to save on thread creation (and just keep a count of the maximum number of parallel operations for their own tasks), so even switching between different dispatchers that each use thread pools may not result in using a different thread.
As far as people saying "they don't necessarily come back to the same thread," this is correct, for similar reasons. Keep in mind that your dispatcher may have many threads, and the one that you were using before you got suspended might be occupied with a different function right now, so the dispatcher will simply pick a different thread to run your code on.
When the coroutine suspends, the underlying Java method returns a special COROUTINE_SUSPENDED value. If the calling function is also suspendable, it also returns the object, and so the execution returns to the innermost plain, non-suspendable function. This function typically runs an event loop, where event handlers are calls to continuation.resume(). So now it is ready to take the next handler from the queue and resume another coroutine.
When you call continuation.resume(), the continuation itself knows about the dispatcher in charge of the coroutine and delegates to it. If the current thread isn't owned by that dispatcher, it dispatches an event to another event loop, the one served by the dispatcher's thread pool. This way the dispatcher controls the thread where the corutine resumes.
I am trying to make an application that constantly hits the google API to fetch the distance between two points and then process the distance and add it in db inside a loop.
I was previously using the volley but it does not waits and my function proceeds with the rest of lines of code. I need help so that the line of code waits for that method to return only if some result is received once I hit google API, then use that result in next line of code.
I'd recommend looking into coroutines. More specifically take a look at the async launcher which should return the value to an awaiting variable. Your code runs asynchronously (starting a new thread so your main UI thread doesn't block and the user can freely do as they wish) and when the call returns your code is called once again at the point where the variable will be awaiting. You could set a timer right before starting the async call and stop it after await.
You could also use withContext{} to await for a result. The code will be continued in the calling function when the coroutine returns a result.
startTimer()
val job = coroutineScope{
async{
networkCall()
}
}
job.await()
stopTimer()
or
startTimer()
coroutineScope{
launch{
withContext{
networkCall()
}
//code will continue when networkCall() has returned a result
stopTimer() //be careful, this may be called through a background thread.
}
}
Coroutines are thread-safe and lightweight so you can start as many as you want without any problems. Also, by using coroutineScope you will avoid lifecycle problems like your ui being rotated which would normally cause a network call to be resent. With coroutineScope your call will have the life span of the calling activity/fragment thus it will be cancelled or killed if your app unexpectedly decides to stop and not care for the result any longer.
Hope this helped, Panos.
If you are beginner use retrofit library to make API calls in there is option enque will call API background for you.
https://www.journaldev.com/13639/retrofit-android-example-tutorial
If you are pro go for Rxjava with Retrofit.
https://medium.com/3xplore/handling-api-calls-using-retrofit-2-and-rxjava-2-1871c891b6ae
You cannot do synchronous calls on the main thread. Blocking the UI thread on a network call for more than a specified period of time would trigger an ANR.
A couple of options would be to use an AysncTask or AsyncTaskLoader. Blocking the main thread is definitely not recommended.
AsyncTasks create a separate thread of execution while the UI thread can continue with its work.
Android applications usually start with a single thread of execution so if you block this thread then an ANR would result.
Take a look here
If you don't mind the ANRs which will not be acceptable to a user then go with using a simple HttpURLConnection but this is not recommended.
If you do not prefer the AsyncTask approach you can create a Runnable to do the background processing and then update the UI thread.
More here
You can implement a jobservice to get distance in an interval.You can view the implementation .here