CoroutineScope cancelation - android

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.

Related

Could it be that Flow.collect() blocks the UI?

I'm reading the article about flows on kotlinlang.org: https://kotlinlang.org/docs/flow.html
They show the next example:
fun simple(): Flow<Int> = flow {
println("Flow started")
for (i in 1..3) {
delay(100)
emit(I)
}
}
fun main() = runBlocking<Unit> {
println("Calling simple function...")
val flow = simple()
println("Calling collect...")
flow.collect { value -> println(value) }
println("Calling collect again...")
flow.collect { value -> println(value) }
}
And they say that the output is:
Calling simple function...
Calling collect...
Flow started
1
2
3
Calling collect again...
Flow started
1
2
3
Therefore, it seems like the UI thread is waiting for the first flow.collect function to finish, before continuing to print "Calling collect again..."
I would expect that while the first flow builder runs, the system will print "Calling collect again...", so the output would be:
Calling simple function...
Calling collect...
Calling collect again...
Flow started
1
2
3
Flow started
1
2
3
What am I missing?
collect is a suspend function. Suspend functions are synchronous in the calling code, so collect is no different than calling forEach on a List, as far as code execution order is concerned.
It's not blocking the calling thread, but it is suspending, which means the code in the coroutine waits for it to return before continuing. This is the primary feature of coroutines--that you can call time-consuming code synchronously without blocking the thread. Under the hood, the suspend function is doing something asynchronously, but to the coroutine that calls it, it is treated as synchronous.
Under the hood, the coroutine dispatcher is breaking up your coroutine into chunks that it passes to the calling thread to run. In between these chunks, where it is suspending, the calling thread is free to do other stuff, so it's not blocked.
My answer here might help with explaining the concept further.

Parent and Child Coroutines not getting cancelled

I have a Parent Coroutine with a Child Coroutine like this:
val exceptionHandler = CoroutineExceptionHandler {_,e -> println("exception $e")}
val scope = CoroutineScope(Dispatchers.Main)
mainCoroutineJob = scope.launch(exceptionHandler){
val data = withContext(Dispatchers.IO){
val data = getData()
data
}
data?.let{
// do something with data
}
}
When I try to cancel both the parent and child coroutines using this:
mainCoroutineJob.cancel("Coroutine Cancelled")
mainCoroutineJob.cancelChildren(CancellationException("Coroutine Cancelled"))
the code inside withContext, keeps on running.
May I know why? And how can we cancel the withContext as well?
withContext is a suspend function, not a coroutine builder, therefore there's no child coroutine. The reason why it's not stopping when the job is cancelled is because it is not cooperative. You need to make your getData cooperatively cancellable. I assume it is already a suspend function, if so, then you just need to check at critical points whether the job that is running that suspend function still active and proceed only if it is. You can check it by using coroutineContext.isActive inside of a suspend function.
A critical point might be a loop or if there's no loop but a function is still doing some heavy processing you can divide your function into chunks and then before proceeding into processing the next chunk you check whether the suspend function's job is still active or not, and proceed only if it is active.

Does the suspend keyword in Kotlin do anything without coroutines?

My understanding is that Kotlin's coroutines are libraries, which leaves the only language-level feature of concurrency in Kotlin as the suspend keyword.
I'm still wrapping my head around coroutines in Kotlin, but I'm wondering if that may be overkill for my problem, which is updating a text view as soon as an HttpsURLConnection returns data. exception handling makes callbacks ugly enough that I want to avoid those if possible
does the suspend keyword simply mean that the runtime may suspend a function that takes a while to complete? or is suspension only enabled inside a coroutine? as a hypothetical, can I write
suspend fun getStringFromNetwork(): String {
val request = URL("https:stackoverflow.com").openConnection()
val result = readStream(request.inputStream)
request.disconnect()
return result
}
//and then elsewhere
foo()
val s = getStringFromNetwork()
bar(s)
baz()
and know that if getStringFromNetwork downloads 1 GB of data that baz() will be called in the meantime, while bar(s) waits for s to be populated by getStringFromNetwork?
The "and then elsewhere" part calls getStringFromNetwork(), so it won't compile outside a suspend function (including suspend lambdas), and they can only be executed inside coroutines.
that baz() will be called in the meantime, while bar(s) waits for s to be populated by getStringFromNetwork?
No, if you write it this way, baz() will only start executing after bar(s) returns. But of course bar(s) can start a new coroutine which will do the actual work.

Kotlin coroutines `runBlocking`

I am learning Kotlin coroutines. I've read that runBlocking is the way to bridge synchronous and asynchronous code. But what is the performance gain if the runBlocking stops the UI thread?
For example, I need to query a database in Android:
val result: Int
get() = runBlocking { queryDatabase().await() }
private fun queryDatabase(): Deferred<Int> {
return async {
var cursor: Cursor? = null
var queryResult: Int = 0
val sqlQuery = "SELECT COUNT(ID) FROM TABLE..."
try {
cursor = getHelper().readableDatabase.query(sqlQuery)
cursor?.moveToFirst()
queryResult = cursor?.getInt(0) ?: 0
} catch (e: Exception) {
Log.e(TAG, e.localizedMessage)
} finally {
cursor?.close()
}
return#async queryResult
}
}
Querying the database would stop the main thread, so it seems that it would take the same amount of time as synchronous code? Please correct me if I am missing something.
runBlocking is the way to bridge synchronous and asynchronous code
I keep bumping into this phrase and it's very misleading.
runBlocking is almost never a tool you use in production. It undoes the asynchronous, non-blocking nature of coroutines. You can use it if you happen to already have some coroutine-based code that you want to use in a context where coroutines provide no value: in blocking calls. One typical use is JUnit testing, where the test method must just sit and wait for the coroutine to complete.
You can also use it while playing around with coroutines, inside your main method.
The misuse of runBlocking has become so widespread that the Kotlin team actually tried to add a fail-fast check which would immediately crash your code if you call it on the UI thread. By the time they did this, it was already breaking so much code that they had to remove it.
Actually you use runBlocking to call suspending functions in "blocking" code that otherwise wouldn't be callable there or in other words: you use it to call suspend functions outside of the coroutine context (in your example the block passed to async is the suspend function). Also (more obvious, as the name itself implies already), the call then is a blocking call. So in your example it is executed as if there wasn't something like async in place. It waits (blocks interruptibly) until everything within the runBlocking-block is finished.
For example assume a function in your library as follows:
suspend fun demo() : Any = TODO()
This method would not be callable from, e.g. main. For such a case you use runBlocking then, e.g.:
fun main(args: Array<String>) {
// demo() // this alone wouldn't compile... Error:() Kotlin: Suspend function 'demo' should be called only from a coroutine or another suspend function
// whereas the following works as intended:
runBlocking {
demo()
} // it also waits until demo()-call is finished which wouldn't happen if you use launch
}
Regarding performance gain: actually your application may rather be more responsive instead of being more performant (sometimes also more performant, e.g. if you have multiple parallel actions instead of several sequential ones). In your example however you already block when you assign the variable, so I would say that your app doesn't get more responsive yet. You may rather want to call your query asynchronously and then update the UI as soon as the response is available. So you basically just omit runBlocking and rather use something like launch. You may also be interested in Guide to UI programming with coroutines.

Kotlin Android Coroutines - suspend function doesn't seem to run in the background

I feel like i'm missing some crucial part to my understanding to how this code below is working:
private fun retrieveAndStore() {
launch(UI) {
val service = retrofit.create(AWSService::class.java)
val response = service.retrieveData().await()
store(data = response)
}
}
private suspend fun store(data: JsonData) {
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app-db").build()
db.appDao().insert(storyData)
}
This is the error i get when run:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
I don't understand why the network code via retrofit works but the store function fails. I'm hoping someone can tell me what's going on?
Interestingly if i wrap db call with async {}.await it works, does that mean coroutines can only call other coroutines?
Coroutines aren't about running in either foreground or background. They are about the ability to get suspended, just like a native thread gets suspended by the OS, but on the level where you are in control of that behavior.
When you say launch(UI) { some code }, you tell Kotlin to submit "some code" as a task to the GUI event loop. It will run on the GUI thread until explicitly suspended; the only difference is that it won't run right away so the next line of code below the launch(UI) block will run before it.
The magic part comes around when your "some code" encounters a suspendCoroutine call: this is where its execution stops and you get a continuation object inside the block you pass to suspendCoroutine. You can do with that object whatever you want, typically store it somewhere and then resume it later on.
Often you don't see the suspendCoroutine call because it's inside the implementation of some suspend fun you're calling, but you can freely implement your own.
One such library function is withContext and it's the one you need to solve your problem. It creates another coroutine with the block you pass it, submits that coroutine to some other context you specify (a useful example is CommonPool) and then suspends the current coroutine until that other one completes. This is exactly what you need to turn a blocking call into a suspendable function.
In your case, it would look like this:
private suspend fun store(data: JsonData) = withContext(CommonPool) {
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app-db").build()
db.appDao().insert(storyData)
}
I'll also add that you're better off creating your own threadpool instead of relying on the system-wide CommonPool. Refer to this thread for details.

Categories

Resources