Android and Kotlin coroutines: inappropriate block

2020-08-14 07:42发布

问题:

I have the following class:

class Repository(
    private val assetManager: AssetManager,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
    suspend fun fetchHeritagesList(): HeritageResponse = withContext(ioDispatcher) {
        try {
            // TODO Blocking method call?
            val bufferReader = assetManager.open("heritages.json").bufferedReader()
...

and I'm wondering why do I get a warning in the open("heritages.json") saying Innapropriate blocking method call? isn't the withContext(ioDispatcher) fixing that?

Thanks for the explanation!

回答1:

IntelliJ inspection that looks for blocking calls inside suspendable functions isn't powerful enough to see through a level of indirection between Dispatchers.IO and its usage in withContext. Here's a minimal reproducer of the issue:

class IoTest {
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO

    suspend fun indirectRef() = withContext(ioDispatcher) {
        Thread.sleep(1) // Flagged as inappropriate blocking call
    }

    suspend fun directRef() = withContext(Dispatchers.IO) {
        Thread.sleep(1) // Not flagged
    }
}

However, unlike in my case, your ioDispatcher is exposed for injection through the constructor so you could just as easily supply Dispatchers.Main instead of it, and that would constitute inappropriate blocking.

Unfortunately I haven't yet heard of any way to formally specify the contract of a dispatcher as "tolerating blocking calls", so that you could enforce it in the constructor.

There is already a similar issue open on YouTrack.



回答2:

Code below can hang, because of blocking IO:

class MyService(assetManager: AssetManager) {
   private val repo = Repository(assetManager, newFixedThreadPoolContext(nThreads: 1, name: "SO QA"))

   suspend fun read(): HeritageResponse {
      return fetchHeritagesList(); // <-- please note, that we replaced IO dispatcher to single-threaded one
   }
}