Issue
Background
I'm having problems in migrating from the simple (deprecated) AsyncTask
and Executors
to Kotlin Coroutines
on Android
The problem
I can't find how I can perform the basic things I could have done on AsyncTask
and even on Executors
using Kotlin Coroutines
.
In the past, I could choose to cancel a task with and without thread interruption. Now for some reason, given a task that I create on Coroutines, it's only without interruption, which means that if I run some code that has even "sleep" in it (not always by me), it won't be interrupted.
I also remember I was told somewhere that Coroutines is very nice on Android, as it automatically cancel all tasks if you are in the Activity. I couldn't find an explanation of how to do it though.
What I've tried and found
For the Coroutines task (called Deferred
according to what I see) I think I've read that when I create it, I have to choose which cancellation it will support, and that for some reason I can't have them both. Not sure if this is true, but I still wanted to find out, as I want to have both for best migration. Using AsyncTask, I used to add them to a set (and remove when cancelled) so that upon Activity being finished, I could go over all and cancel them all. I even made a nice class to do it for me.
This is what I've created to test this:
class MainActivity : AppCompatActivity() {
val uiScope = CoroutineScope(Dispatchers.Main)
val bgDispatcher: CoroutineDispatcher = Dispatchers.IO
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadData()
}
private fun loadData(): Job = uiScope.launch {
Log.d("AppLog", "loadData")
val task = async(bgDispatcher) {
Log.d("AppLog", "bg start")
try {
Thread.sleep(5000L) //this could be any executing of code, including things not editable
} catch (e: Exception) {
Log.d("AppLog", "$e")
}
Log.d("AppLog", "bg done this.isActive?${this.isActive}")
return@async 123
}
//simulation of cancellation for any reason, sadly without the ability to cancel with interruption
Handler(mainLooper).postDelayed({
task.cancel()
}, 2000L)
val result: Int = task.await()
Log.d("AppLog", "got result:$result") // this is called even if you change orientation, which I might not want when done in Activity
}
}
build gradle file:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1"
The questions
- Is it possible on Coroutines to have a task that I can cancel with and without thread interruption?
- What should be added to make this work, so that when the Activity dies (orientation change, for example), it would be auto-cancelled (with the choice of with or without interruption) ? I guess I could use a similar solution as I had for AsyncTask, but I remember I was told there is a nice way to do it for Coroutines too.
Solution
By default, coroutines do not do thread interrupts - as per the Making computation code cancellable documentation, using yield()
or checking isActive
allows coroutine aware code to participate in cancellation.
However, when interfacing with blocking code where you do want a thread interrupt, this is precisely the use case for runInterruptible(), which will cause the contained code to be thread interrupted when the coroutine scope is cancelled.
This works perfectly with lifecycle-aware coroutine scopes, which automatically cancel when the Lifecycle
is destroyed:
class MainActivity : AppCompatActivity() {
val bgDispatcher: CoroutineDispatcher = Dispatchers.IO
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadData()
}
private fun loadData(): Job = lifecycleScope.launch {
Log.d("AppLog", "loadData")
val result = runInterruptible(bgDispatcher) {
Log.d("AppLog", "bg start")
try {
Thread.sleep(5000L) //this could be any executing of code, including things not editable
} catch (e: Exception) {
Log.d("AppLog", "$e")
}
Log.d("AppLog", "bg done this.isActive?${this.isActive}")
return@runInterruptible 123
}
Log.d("AppLog", "got result:$result")
}
}
Answered By - ianhanniballake
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.