Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

242
Views
Android Kotlin Coroutines Freeze

I have come across an interesting coroutines freeze that I have simplified into the following problem:

//running on main thread
runBlocking {
   lifecycleScope.launch {
      delay(1000)
   }.join()
}

This causes the main thread to freeze indefinitely. I assume it is because of the following sequence of events:

  1. Queue to launch
  2. Call to join, pass main thread to coroutine pool
  3. Call to launch
  4. Call to delay, pass main thread to coroutine pool
  5. Thread moves back to join and waits
  6. Delay never finishes because it does not have a thread available?

Correct me if I am misunderstanding the above logic. What is a reasonable pattern to avoid this from happening? I understand the running blocking on the main thread is not a good idea, but deeper in the code it seems odd that you can accidentally freeze a single thread coroutine in this manner.

over 4 years ago · Santiago Trujillo
3 answers
Answer question

0

It's because of calling runBlocking on the main thread (which defeats the idea of even using coroutines); the order of events might not matter, when the top-most instruction already stalls the thread. It's always GlobalScope vs. CoroutineScope vs. lifecycleScope ...where lifecycleScope.launch can be used with different dispatchers:

  • lifecycleScope.launch(Dispatchers.IO): Launches a coroutine within the lifecycleScope provided by AndroidX. Coroutine gets cancelled as soon as lifecycle is invalidated (i.e. user navigates away from a fragment). Uses Dispatchers.IO as thread pool.

  • lifecycleScope.launch: Same as above, but uses Dispatchers.Main if not specified.

Therefore I'd assume, the behavior also may stem from dispatching with Dispatchers.Main.

over 4 years ago · Santiago Trujillo Report

0

It is even simpler than you think. Because of runBlocking(), join() doesn't return the thread to the event loop, so the launch() block never starts executing - deadlock.

Actually... this is not entirely true. join() returns the thread to the pool, but not to the one we think about. runBlocking() starts its own event loop using the caller thread. From the outside of runBlocking() the thread seems to be constantly blocked, but in the inside it loops and can suspend. Anyway, from the perspective of lifecycleScope the main thread is blocked and it can't launch anything on it.

What is a reasonable pattern to avoid this from happening?

Do not call runBlocking() on the main thread. Coroutines are no exception here. We should not run blocking IO or other kind of blocking operations on the main thread and that includes runBlocking().

over 4 years ago · Santiago Trujillo Report

0

Here's an explanation about what exactly causes the deadlock.

Any code anywhere in your app that is running on the Main thread is actually running from a message that has been sent to the Main Looper's queue of messages to process on the main thread.

The way Dispatchers.Main works is that it essentially sends pieces of coroutines as Runnable messages to an Android Handler that is backed by the Main Looper. Messages sent to the Main Looper can only be processed one at a time.

Inside your runBlocking call, your join() call is suspending until its associated coroutine finishes. That coroutine has been submitted to the main Looper. The Looper cannot process any messages in its queue until the current message returns. The current message is whichever one ran the method on the main thread that you called runBlocking from.

runBlocking is waiting on join() to return. join() is waiting for its coroutine to get processed by the Looper. The Looper is waiting on runBlocking to return.

I saw you mentioned in a comment that it works with GlobalScope. This is because GlobalScope uses Dispatchers.Default and lifecycleScope uses Dispatchers.Main (unless you modify the default context when launching the coroutine).

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!