Why does this coroutine block UI Thread? - android

Deprecation alert
This code uses the old Coroutines Api. If you're using kotlinx-coroutines 1.1.0 or newer, this code won't be useful to you
The original question was:
I'm finding that this particular code in my Android App blocks the UI Thread:
runBlocking {
async(CommonPool) {
Thread.sleep(5000)
}.await()
}
textView.text = "Finish!"
I've been using coroutines for several tasks, and they never block UI Thread, as can be read in the documentation:
. Coroutines provide a way to avoid blocking a thread and replace it with a cheaper and more controllable operation: suspension of a coroutine
But curiously, this code:
runBlocking {
async(CommonPool) {
launch(CommonPool) {
Thread.sleep(5000)
runOnUiThread { textView.text = "Finish!" }
}
}.await()
}
behaves as expected; does not block, waits five seconds then prints the result (I need to update the UI after, and only after the sleep is completed)
The documentation says that async and launch can be used independently and don't need to be combined. In fact, async(CommonPool) should be enough.
So what's really going on here? why does it work only with async+launch ?
Update (2021)
[Deprecation alert] This code uses the old Coroutines Api. If you're using kotlinx-coroutines 1.1.0 or newer, forget about this code
My full sample code:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button1.setOnClickListener {
runBlocking {
async(CommonPool) {
Thread.sleep(5000L)
}.await()
}
textView1.text = "Finally! I've been blocked for 5s :-("
}
button2.setOnClickListener {
runBlocking {
async(CommonPool) {
launch(CommonPool) {
Thread.sleep(5000L)
runOnUiThread { textView1.text = "Done! UI was not blocked :-)" }
}
}.await()
}
}
}
}

Note: this post dates back to the pre-release version of coroutines. I updated the names of dispatchers to match the release version.
runBlocking is not the way to start a coroutine on the UI thread because, as its name says, it will block the hosting thread until the coroutine is done. You have to launch it in the Main context and then switch to the Default context for the heavyweight operation. You should also drop the async-await pair and use withContext:
button1.setOnClickListener {
launch(Main) {
withContext(Default) {
Thread.sleep(5000L)
}
textView1.text = "Done! UI was not blocked :-)"
}
}
withContext will suspend the coroutine until done and then resume it in the parent context, which is Main.

As documentation tells us:
[runBlocking] runs new coroutine and blocks current thread interruptibly until its completion. This function should not be used from coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.
As quoted, it’s often used in tests that work with regular coroutines and also in main methods to wait for the completion of coroutines.
Also, this tutorial will help understanding its use cases.

I'm sorry for this late answer, but I hope you find it helpful.
1- Because in the first situation
runBlocking {
async(CommonPool) {
Thread.sleep(5000L)
}.await()
}
the runBlocking{} block will block the main thread until the code inside it finishes, then when we go inside the runBlocking{} block we find that you awaited on the async{} block, so we must wait until what's inside async{} finishes so we must wait for the 5 seconds. That why this code blocks the main thread.
2- But in the second situation:-
runBlocking {
async(CommonPool) {
launch(CommonPool) {
Thread.sleep(5000)
runOnUiThread { textView.text = "Finish!" }
}
}.await()
}
You didn't await on the launch{} block (via using .join()), so you the only thing you did inside the async{} block is launching the coroutine without waiting for it to finish. It is like that the async{} block is empty, so the async{}.await() and runBlocking{} will not wait for anything to finish. That's why this second situation doesn't block the main thread.
I hope this answers it.

Related

How to understand and improve async-await in coroutines?

I would like to improve my async-await in coroutines.
This is my solution
val coroutineContext: CoroutineContext
get() = job + Dispatchers.IO
[...]
lifecycleScope.launch(coroutineContext) {
async {
client = viewModel.getClientItem(clientId)
}.await()
if (inEditMode != null) {
changedEditMode = true
}
[...]
#Marko Topolnik wrote in Why does this coroutine block UI Thread?
Note: this post dates back to the pre-release version of coroutines. I updated the names of dispatchers to match the release version.
runBlocking is not the way to start a coroutine on the UI thread because, as its name says, it will block the hosting thread until the coroutine is done. You have to launch it in the Main context and then switch to the Default context for the heavyweight operation. You should also drop the async-await pair and use withContext:
button1.setOnClickListener {
launch(Main) {
withContext(Default) {
Thread.sleep(5000L)
}
textView1.text = "Done! UI was not blocked :-)"
}
}
withContext will suspend the coroutine until done and then resume it in the parent context, which is Main.
Why doing it with Main Dispatchers and then withContext using Default dispatchers is betters solution than async. It will also block main thread and work the same what is the difference. And how to handle this approaches?
Greetings
EDIT:
My other solution is this below or withContext(Dispatchers.IO)
lifecycleScope.launch(Dispatchers.Main) {
withContext(Dispatchers.Default) {
client = viewModel.getItem(clientId)
}
[...]
}
Now as I read I do not use runBlocking and don't block main thread?
In that configuration it works the same as with async-await.
In your opinin that's better solution
?
Why doing it with Main Dispatchers and then withContext using Default dispatchers is betters solution than async. It will also block main thread and work the same what is the difference
withContext doesn't block, it suspends. The terminology might should similar but the behaviour is very different. While Thread.sleep is executing on the Default dispatcher (and thus on one of this dispatcher's threads), the main thread can keep running other code. The coroutine launched via launch(Main) here is in a suspended state, which means the main dispatcher knows it can execute other things instead. The linked answer here explains why runBlocking was a bad idea, because no matter what you launch in it runBlocking has to block the current thread while it's executing coroutines inside.
Now back to your code. Note that async { doStuff() }.await() makes little sense. It is equivalent to simply doStuff().
Another thing is that you're passing a job explicitly to launch via the context here. This doesn't seem right, and could lead to problems. If you want to use Dispatchers.IO, you don't need to pass a job too.
That said, even Dispatchers.IO seems like a stretch here. You should instead make viewModel.getClientItem a suspend function (if not already), and leave the responsibility of using the right dispatcher to this function. Maybe you don't even need IO at all here, or maybe it's an HTTP or DB call that already has its own thread pool anyway.
So I would replace your whole code with:
lifecycleScope.launch {
client = viewModel.getClientItem(clientId)
if (inEditMode != null) {
hangedEditMode = true
}
}

Why and how Kotlin coroutine prevents blocking of a thread, even without "suspend" keyword?

I came across some unexpected behavior while using coroutines in Android app.
Suppose I've got the following function, which is not "suspend". It starts worker threads and should block the calling thread until all workers terminate:
fun doSomething() : Result {
// producers init thread
Thread {
for (i in 0 until NUM_OF_MESSAGES) {
startNewProducer(i) // each producer is a thread
}
}.start()
// consumers init thread
Thread {
for (i in 0 until NUM_OF_MESSAGES) {
startNewConsumer() // each consumer is a thread
}
}.start()
synchronized(lock) {
while (numOfFinishedConsumers < NUM_OF_MESSAGES) {
try {
(lock as java.lang.Object).wait()
} catch (e: InterruptedException) {
return#synchronized
}
}
}
synchronized(lock) {
return Result(
System.currentTimeMillis() - startTimestamp,
numOfReceivedMessages
)
}
}
I know that (lock as java.lang.Object).wait() is ugly and I'd be better off with ReentrantLock, but I intentionally wanted to fall to the most primitive level.
Now, if I execute this function without coroutine from Android's main thread, it blocks the calling thread (expected behavior):
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
someObject.doSomething()
}
However, if I just wrap it in a coroutine which is also executed on main thread, main thread isn't blocked anymore, but the functionality remains the same:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
CoroutineScope(Dispatchers.Main).launch {
val result = someObject.doSomething()
}
}
Two questions:
I thought that in order for coroutines to work, the functions should be "suspend", but it's not the case here. So, what's the point of "suspend" then?
Call to (lock as java.lang.Object).wait() should've blocked the main thread. How comes it doesn't when coroutine is involved? Do coroutines have a mean of "intercepting" such low level interactions?
Thanks
Like post() on a View, launch() (usually) schedules work to be performed asynchronously with respect to the current bit of execution. So, the code in your lambda expression passed to launch() will be run on the main application thread eventually, just as the Runnable that you supply to post() will be run on the main application thread eventually. However, your onCreate() function will continue past the point of launch() to do whatever else it is supposed to.
However, just like a Runnable passed to post() could still tie up the main application thread due to what work it does in run(), your coroutine could still tie up the main application thread. It's just that this work would happen later than it would if you did the work directly in onCreate().
It's just that animations still work
IIRC, on newer versions of Android, animations themselves get processed on a separate "render" thread.

How Kotlin coroutines are scheduled

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)

Is NetworkOnMainThreadException valid for a network call in a coroutine?

I'm putting together a simple demo app in Kotlin for Android that retrieves the title of a webpage with Jsoup. I'm conducting the network call using Dispatchers.Main as context.
My understanding of coroutines is that if I call launch on the Dispatchers.Main it does run on the main thread, but suspends the execution so as to not block the thread.
My understanding of android.os.NetworkOnMainThreadException is that it exists because network operations are heavy and when run on the main thread will block it.
So my question is, given that a coroutine does not block the thread it is run in, is a NetworkOnMainThreadException really valid? Here is some sample code that throws the given Exception at Jsoup.connect(url).get():
class MainActivity : AppCompatActivity() {
val job = Job()
val mainScope = CoroutineScope(Dispatchers.Main + job)
// called from onCreate()
private fun printTitle() {
mainScope.launch {
val url ="https://kotlinlang.org"
val document = Jsoup.connect(url).get()
Log.d("MainActivity", document.title())
// ... update UI with title
}
}
}
I know I can simply run this using the Dispatchers.IO context and providing this result to the main/UI thread, but that seems to dodge some of the utility of coroutines.
For reference, I'm using Kotlin 1.3.
My understanding of coroutines is that if I call launch on the Dispatchers.Main it does run on the main thread, but suspends the execution so as to not block the thread.
The only points where execution is suspended so as to not block the thread is on methods marked as suspend - i.e., suspending methods.
As Jsoup.connect(url).get() is not a suspending method, it blocks the current thread. As you're using Dispatchers.Main, the current thread is the main thread and your network operation runs directly on the main thread, causing the NetworkOnMainThreadException.
Blocking work like your get() method can be made suspending by wrapping it in withContext(), which is a suspending method and ensures that the Dispatchers.Main is not blocked while the method runs.
mainScope.launch {
val url ="https://kotlinlang.org"
val document = withContext(Dispatchers.IO) {
Jsoup.connect(url).get()
}
Log.d("MainActivity", document.title())
// ... update UI with title
}
Coroutine suspension is not a feature that magically "unblocks" an existing blocking network call. It is strictly a cooperative feature and requires the code to explicitly call suspendCancellableCoroutine. Because you're using some pre-existing blocking IO API, the coroutine blocks its calling thread.
To truly leverage the power of suspendable code you must use a non-blocking IO API, one which lets you make a request and supply a callback the API will call when the result is ready. For example:
NonBlockingHttp.sendRequest("https://example.org/document",
onSuccess = { println("Received document $it") },
onFailure = { Log.e("Failed to fetch the document", it) }
)
With this kind of API no thread will be blocked, whether or not you use coroutines. However, compared to blocking API, its usage is quite unwieldy and messy. This is what coroutines help you with: they allow you to continue writing code in exactly the same shape as if it was blocking, only it's not. To get it, you must first write a suspend fun that translates the API you have into coroutine suspension:
suspend fun fetchDocument(url: String): String = suspendCancellableCoroutine { cont ->
NonBlockingHttp.sendRequest(url,
onSuccess = { cont.resume(it) },
onFailure = { cont.resumeWithException(it) }
)
}
Now your calling code goes back to this:
try {
val document = fetchDocument("https://example.org/document")
println("Received document $document")
} catch (e: Exception) {
Log.e("Failed to fetch the document", e)
}
If, instead, you're fine with keeping your blocking network IO, which means you need a dedicated thread for each concurrent network call, then without coroutines you'd have to use something like an async task, Anko's bg etc. These approaches also require you to supply callbacks, so coroutines can once again help you to keep the natural programming model. The core coroutines library already comes with all the parts you need:
A specialized elastic thread pool that always starts a new thread if all are currently blocked (accessible via Dispatchers.IO)
The withContext primitive, which allows your coroutine to jump from one thread to another and then back
With these tools you can simply write
try {
val document = withContext(Dispatchers.IO) {
JSoup.connect("https://example.org/document").get()
}
println("Received document $it")
} catch (e: Exception) {
Log.e("Failed to fetch the document")
}
When your coroutine arrives at the JSoup call, it will set the UI thread free and execute this line on a thread in the IO thread pool. When it unblocks and gets the result, the coroutine will jump back to the UI thread.

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.

Categories

Resources