Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  1. 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?
  2. 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 image 546
Vasiliy Avatar asked Jan 25 '23 17:01

Vasiliy


1 Answers

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.

like image 172
CommonsWare Avatar answered Jan 29 '23 12:01

CommonsWare