Home > database >  diff between executing coroutine with and without suspend-ed IO methods
diff between executing coroutine with and without suspend-ed IO methods

Time:12-03

Let's assume I have the code:

// Android ViewModel
class MyVM(val dao: MyDao) : ViewModel() {
    fun onButtonInsert() {
        // point 1
        viewModelScope.launch {
            // point 2
            dao.insert_SuspendFun(MyData(id=1, "hello, it works"))
            // point 3
            dao.insert_NOT_SuspendFun(MyData(id=2, "hello, it fails"))
            // point 4
        }
    }
}

// Room DAO
@Dao
interface MyDao {
    @Insert
    suspend fun insert_SuspendFun(md: MyData)
    @Insert
    fun insert_NOT_SuspendFun(md: MyData)
}

Now when fun onButtonInsert runs then:
1st line works:

dao.insert_SuspendFun(MyData(id=1, "hello, it works"))

but 2nd line:

dao.insert_NOT_SuspendFun(MyData(id=2, "hello, it fails"))

fails with the exception:

java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

"The only difference" is that fun insert_NOT_SuspendFun has no suspend keyword.

Both methods run in the same coroutine.

Can someone explain what happens under the hood?
How are threads working in this coroutine?
Why does 1st call use non-UI thread but 2nd uses UI thread?

Thanks! ;]

CodePudding user response:

You're trying to run a long-running synchronize task in side main thread as it does not mention suspend keyword to make it async.

You can use both methods in the IO thread instead main;

viewmodelscope.launch(dispatchers.io)

enter image description here

CodePudding user response:

Room generates the code for the implementation of your DAO, so the behaviors are based on their design decisions.

The non-suspend function runs on whatever thread you call it from, which is the only possible way a non-suspend function can run. Since Google doesn’t want people making janky apps that do IO on the main thread, they decided to make the generated implementation under the hood check the thread and throw an exception if it’s the main thread.

The suspend function implementation that Room generates suspends while the IO is done on a background thread. This follows the Kotlin coroutines convention that a suspend function should never block the thread it is called from.

Any suspend function can internally suspend the calling code until they finish and resume. Other than by calling other suspend functions, this is most commonly done using withContext, coroutineScope, suspendCoroutine, or suspendCancellableCoroutine. Those last two are low level and allow the coroutine to be suspended and then resumed at will from any other thread.

I have not checked the generated source, but I think it’s likely Room generates the suspend function implementation by using suspendCancellableCoroutine and runs the IO work using an internal thread pool before resuming the coroutine.

An alternative way they could have done it is by using withContext(Dispatchers.IO) and then done the blocking IO work within it. This is possibly the most common and easiest way to do blocking work in a coroutine. It uses the IO Dispatcher (a thread pool provided by Kotlin coroutines).

As for why your coroutine runs on the main thread, it’s because viewModelScope’s attached dispatcher uses the main thread. You can override it by passing a specific dispatcher to your launch call, but this is not often done if you’re following conventions. Android is full of functions that must be called from the main thread, so it usually looks cleanest to keep coroutines on the main dispatcher and only do the background work using suspend functions and withContext blocks. Exception would be a coroutine that runs blocking code and doesn’t need to do anything on the main thread.

  • Related