Why invoking join() method, on a coroutine having MainScope() as scope, stopping it?

Please help me in understanding this scenario. Consider following code.

 val scope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    runBlocking {
        val myJob =  scope.launch {
            println("Launching coroutine")
        }
        myJob.join()
    }
}

It prints nothing. If I change the dispatcher like

val scope = MainScope() + Dispatchers.Default

OR did not call join() method then it works i-e prints Launching coroutine

Thank you.

1 Like

The problem is that you are deadlocking on the main thread here.

Always remember that runBlocking blocks the current thread while its body is executing, so you need to use it with lots of care. Let’s break down what happens in your case:

  1. onCreate() is called on the main thread
  2. runBlocking blocks the current thread (the main thread) to run the lambda
  3. scope.launch{} launches a coroutine in scope (which is MainScope()) so it uses Dispatchers.Main, which in turn needs the main thread. It is therefore kinda “enqueued” (in the Main dispatcher) and waits for the main thread to be free so it can run
  4. myJob.join() suspends until the launched coroutine is complete, which means runBlocking continues blocking the main thread to wait for it

In this situation, the launched coroutine waits for the main thread to be free, and the runBlocking waits for the coroutine to be complete, and blocks the main thread while doing so. The main thread is therefore never free. Deadlock.

Note that using an external scope to run stuff while inside runBlocking is quite confusing, because coroutine builders like runBlocking already provide a scope to you as this in the lambda. By not using it, you’re effectively running your other coroutine completely independently from runBlocking, without structured concurrency. This is rather bad practice.

Now, what exactly are you trying to do here? If you launch and immediately join the job, you may as well just write the stuff that’s inside the launch directly in the runBlocking body instead:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    runBlocking {
        println("Launching coroutine") // assuming you run suspending stuff here, otherwise no need for `runBlocking`
    }
}
4 Likes

@joffrey.bion Thank you for the detail explanation.