so I've been looking at the coroutines for past few days and found quite interesting piece of code
val input = MutableStateFlow(5)
lifecycleScope.launch {
input.collectLatest {
val job = launch {
doSomething1()
delay(1000L)
doSomething2()
}
job.join()
}
}
I've been debugging it and im not sure why do we need that job.join in there, how I understand the whole flow right now:
Lets say we push fast 3 values to input, collectLatest will terminate previous unfinished box, but since we are launching new coroutine its job is not terminated, so after pushing fast 3 values we will always reach doSomething2 for each value, if we would skip launch inside collectLatest we would reach only one doSomething2 - but now coming to the job.join - it should suspend coroutine until the job is done, but since we are not doing anything else in this coroutine is it really needed? Does it give any value? From my tests it doesn't really matter if we have that job.join or not, what I am missing in here?
Related
I have a flow that does CPU intensive work as shown below:
fun doWork():Flow<MyResult> =
flow{
for(i in 1..100){
//calculate()
}
emit(MyResult())
}
I collect from it inside a Fragment as shown below:
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
launch {
viewModel.doWork().collect {
val result = it ?: return#collect
// Preview result
}
}
}
But, since I am collecting using the main thread, the flow body runs on the main thread which is not the best thing to do.
How can I make the flow execute on a different thread?
In case you intend to do heavy calculations inside a flow it is recommended to move it to the default dispatcher like so,
flow{
//CPU intensive work
}.flowOn(your chosen dispatcher)
Libraries like the Room database and retrofit for networking handle the correct threading under the hood for you, so you do not have to worry about using .flowOn
Using withContext() inside a flow will not work for you.
This link is also very useful if you want to know more about asynchronous Flow.
This comes from near the end of the codelab found here:
Intro to debugging - Debugging example: accessing a value that doesn't exist
This is all inside the MainActivity.kt file
Here's my onCreate
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val helloTextView: TextView = findViewById(R.id.division_textview)
helloTextView.text = "Hello, debugging!"
division()
}
//...
Here's the division function provided by Google, but with 2 changes I'll explain in a moment...
fun division() {
val numerator = 60
var denominator = 4
repeat(4) {
Thread.sleep(3000)
findViewById<TextView>(R.id.division_textview).setText("${numerator / denominator}")
Log.v(TAG, "${numerator / denominator}")
denominator--
}
}
The instructions make it seem like they expect sleep() to accept seconds, but AS indicates it expects millis, so I changed their 3 to 3000. I added Log.v(TAG, "${numerator / denominator}") to see what was happening because it wasn't doing as expected.
The point of this was to have the emulator create a gif of the quotients being updated. This is supposed to be helpful when debugging.
Nothing displays on screen, not even the app's name, until the repeat() finishes.
The logs happen in 3 second intervals, as expected.
Why is the layout waiting, and how do I make it update on each iteration of the loop?
I honestly have no idea what that Codelab is doing, based off the code they provide. The app isn't going to render anything (not the layout, not any changes to the layout) before onCreate finishes, and onCreate won't finish until it's run all its code, including that repeat block in the division function it calls.
division isn't starting any worker threads, so all Thread.sleep is doing is blocking the main thread - it's hanging the app. And you're right, sleep does take a millis value, not seconds - I get the feeling they didn't actually run this code, it's full of other mistakes and inconsistencies that honestly made it hard to work out what you were meant to be doing. Change which Log.d call? The ones in onCreate? (They actually mean the Log.v call in division, I assume)
Here's how you'd use a thread in Kotlin - you need to create a new one (so you're off the main thread, so it can actually finish creating the activity and run the UI):
fun division() {
// create a new thread (and start it immediately)
thread(start=true) {
repeat(4) { i ->
Thread.sleep(3000L)
// assuming you've done the ``findViewById`` and assigned it to a variable
runOnUiThread { divisionTextView.text = "$i" }
}
}
}
That's just updating with the current repeat number (i) for brevity, but the important things are:
you're creating a new thread to do the work (and sleeping)
you're using runOnUiThread (which is a method on the Activity) to do the text updating on the main/UI thread (same thing). If you try to touch the UI from another thread it will crash
Another way you can do that is to post a runnable to the UI thread through a view, may as well use that TextView:
divisionTextView.post { divisionTextView.text = "$i" }
Coroutines would be a better idea here (you can run them on the main thread without it blocking, so you don't have to worry about switching threads to update the UI, or any thread safety stuff) but that's the basics of doing a thread. Genuinely have no idea what's going on in that codelab.
Nothing displays on screen, not even the app's name, until the repeat() finishes. The logs happen in 3 second intervals, as expected. Why is the layout waiting?
It is due to Activity life cycle
When the activity is in the Created state, you can't see the UI.
When the activity is in the Started state, UI becomes visible but you can't interact with it.
When the activity is in the Resumed state, UI is visible and you are able to interact with it.
Since you're calling the division() function in the onCreate() callback, you won't see the activity UI.
how do I make it update on each iteration of the loop?
One way of doing that is to run your division() function in a new thread. By doing so, the main tread displays the UI (you'll be able t see it). Then use runOnUiThread to update your division_textview text.
fun division() {
val numerator = 60
var denominator = 4
thread(start=true) {
/* The loop only repeat 4 times to avoid a crash*/
repeat(4) {
Log.v(TAG, "$denominator")
// Set division_textview text to the quotient.
runOnUiThread { findViewById<TextView>(R.id.division_textview).setText("${numerator / denominator}") }
// Wait for 1 second
Thread.sleep(1000L)
denominator--
}
}
}
You can use Corountines. delay function is very easy to use
lifecycleScope.launch {
myTextView.text = "Starting"
delay(1000L)
myTextView.text = "Processing"
delay(2000L)
myTextView.text = "Done"
}
i wanted to create a program in Android Studio, using kotlin, where i use timer which does an action every ~20 milliseconds and runs infinitely. I used something like this:
object : CountDownTimer(10000,20){
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
start()
}
}.start()
But the problem is, after some time, the timer starts slowing down, and when 1 sec should pass, it actually is 6 seconds. Changing the actions to 40 milliseconds or more only helps for a little while, because later it still slows down. I saw that people used timer that actually works like this without any problem, but it was in Java, and when i tried to change it into Kotlin with Android Studio's help (i never used Java), it ended up not working (after launching, the app crashed everytime). Does someone know how to create or use different timer other than CountDownTimer that i showed above which is in kotlin?
Edit:
I forgot to add the code that works for me. It's basically the one i accepted as answer but i had to make some changes to make it work, so here it is:
val timerName = lifecycleScope.launch(Dispatchers.IO) {
while (isActive) {
lifecycleScope.launch {
doSomething()
}
delay(20L)
}
}
It most probably happens because the function in onTick() and onFinish() are invoked within the same coroutine that the counter works, so probably you would need to launch another coroutine there to not block the timer scope
Also this CountDownTimer API is experimental at this moment so I guess it is not really recomended to use it, probably you could do something like this:
val timerJob = scope.launch(Dispatcher.IO) {
while (isActive) {
scope.launch { doSomething() }
delay(20L)
}
}
I didn't test it but it should have exactly same behavior (but not blocking the timer), it will run until the scope will cancel itself (f.e. lifecycle scopes) or you will cancel it manually by calling timerJob.cancel()
The isActive boolean takes care of checking if coroutine is still active (so the loop won't "leak")
Unfortunately if you run some heavy stuff in this doSomething() call that will exceed this 20 milisec delay there will happen some concurrency issues so it should be simple
If your "game" is so heavy that this 20 milisec delay will be too small then most probably using coroutines is not the best approach for your idea
I exactly understand how are suspendCoroutine vs suspendCancellableCoroutine work in my samples. But im wondering why println("I finished") (line 13 - second line in viewscope block) executed after i had called viewScope.cancel(). I can fix it with isActive flag before this line but i don't want to check each line. What am i missing there. How i can cancel scope as well ? Thanks
import kotlinx.coroutines.*
import java.lang.Exception
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
fun main() {
val parentJob = Job()
val viewScope = CoroutineScope(Dispatchers.IO + parentJob)
viewScope.launch {
println(tryMe())
println("I finished")
}
Thread.sleep(2000)
viewScope.cancel()
Thread.sleep(10000)
}
suspend fun tryMe() = suspendCoroutine<String> {
println("I started working")
Thread.sleep(6000)
println("Im still working :O")
it.resume("I returned object at the end :)")
}
suspend fun tryMe2() = suspendCancellableCoroutine<String> {
println("I started working")
Thread.sleep(6000)
println("Im still working :O")
it.resume("I returned object at the end :)")
}
suspend fun tryMe3() = suspendCancellableCoroutine<String> {
it.invokeOnCancellation { println("I canceled did you heard that ?") }
println("I started working")
Thread.sleep(6000)
if (it.isActive)
println("Im still working :O")
it.resume("I returned object at the end :)")
}
If we just call cancel, it doesn’t mean that the coroutine work will just stop. If you’re performing some relatively heavy computation, like reading from multiple files, there’s nothing that automatically stops your code from running. Once job.cancel is called, our coroutine moves to Cancelling state.
Cancellation of coroutine code needs to be coperative
You need to make sure that all the coroutine work you’re implementing is cooperative with cancellation, therefore you need to check for cancellation periodically or before beginning any long running work. For example, if you’re reading multiple files from disk, before you start reading each file, check whether the coroutine was cancelled or not. Like this you avoid doing CPU intensive work when it’s not needed anymore.
All suspend functions from kotlinx.coroutines are cancellable: withContext, delay etc. So if you’re using any of them you don’t need to check for cancellation and stop execution or throw a CancellationException. But, if you’re not using them, to make your coroutine code cooperative by checking job.isActive or ensureActive()
Coroutine cancellation is co-operative
You should check whether the coroutine is still active before println("I finished") if you wish that statement not to be executed if the coroutine is canceled, like it follows:
if (isActive)
println("I finished")
Why is that?
Coroutines are not guranteed to be dispatched on another thread. So, while threads provide means to be aborted, which is implemented either at system–level or user–level in the runtime (e.g. the JVM, or ART), coroutines that are not backed by a thread couldn't be canceled anyhow, because the only thing that could be done is throw an exception, but that would abort the whole current execution context (i.e. thread), where other coroutines may be running.
Other answers talk about heavy computations, but that's plainly wrong. No matter what you're doing, whether computationally heavy or not — coroutines can't be forcefully canceled; their cancellation is only a request to be canceled, it's up to the coroutine body to handle cancellation requests using the CoroutineScope property isActive, which gets a boolean indicating whether the work the coroutine is carrying on should continue, if true; or be canceled, if false.
Using suspendCancellableCoroutine is working as expected. suspendCoroutine doesn't check for the cancellation state of the coroutineScope(internally uses safeContinuation) while suspendCancellableCoroutine does checks for the cancellation via CancellableContinuationImpl and it cancels the resume operation.
I've been reading a lot of articles and watching a lot of videos on Kotlin co-routines lately and, despite my efforts, I still can't make sense of them in my head.
I think I've finally found a way to illustrate my problem:
class MyViewModel() : CoroutineScope {
override val coroutineContext = Dispatchers.Main + Job()
fun foo() = launch(handler) {
Log.e("test", "A")
}
}
class MainActivity : Activity() {
override fun onCreate() {
MainViewModel().foo()
Log.e("test", "B")
}
}
The output of this is:
E/test: B
E/test: A
And I don't get how this can be the case, I'm using only one thread (the main thread). If my code executes sequentially, by the time I reach the line log(B)... log(A) should have already be printed.
Does the coroutines library use other threads internally to accomplish this? This is the only explanation I could come up with but haven't found anything saying so in the docs.
PS: Sorry for throwing android into the mix but this code:
fun main() {
GlobalScope.launch(Dispatchers.Unconfined) { // launch new coroutine in background and continue
print(Thread.currentThread().name + "World!") // print after delay
}
(0 .. 1000).forEach { print(".") }
}
seems to work as expected and prints:
main #coroutine#1World!...........................
because 1 thread == sequential work
Hope my question makes sense, thanks for reading!
Under the hood, the Main dispatcher uses a Handler to post a Runnable to the MessageQueue. Basically, it’ll get added to the end of the event queue. This means it will execute soon, but not immediately. Hence why “B” gets printed before “A”.
You can find more information in this article.
EDIT by OP (read the article above before reading this):
Just wanted to clarify why the android example above was working fine, in case someone is still wondering.
fun main() {
GlobalScope.launch(Dispatchers.Unconfined) { // launch new coroutine in background and continue
print(Thread.currentThread().name + "World!") // print after delay
}
(0 .. 1000).forEach { print(".") }
}
We're setting GlobalScope to use the UNCONFINED dispatcher, and this dispatcher has isDispatchNeeded set to false.
false means "schedule in the current thread", and that's why we see the logs printing sequentially. UNCONFINED should not be used in regular code.
All other dispatchers have isDispatchNeeded set to true even the UI dispatcher. see: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/is-dispatch-needed.html
(btw GlobalScope uses the Default dispatcher if we don't specify one)