32
votes

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.

2

2 Answers

63
votes

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.

30
votes

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.