7

My Android app need to do some file read/write in background (inside a Service), first I use:

CoroutineScope(Dispatchers.IO).launch {
    val fos = openFileOutput(fileName, MODE_PRIVATE)
    val oos = ObjectOutputStream(fos)
    oos.writeObject(myObj)
    oos.close()
}

Every single line inside the block has a warning: "Inappropriate blocking method call"

After search the problem, I think I understand 80% of it. So basically most Coroutine only has 1 thread, if it is blocked, that coroutine will have no thread to do other job. To fix this, we should wrap it inside withContext like this:

CoroutineScope(Dispatchers.IO).launch {
    withContext(Dispatchers.IO) {
        val fos = openFileOutput(fileName, MODE_PRIVATE)
        val oos = ObjectOutputStream(fos)
        oos.writeObject(myObj)
        oos.close()
    }
}

Android Studio still shows the warning. The post say it is just a bug in Android Studio, and this solution is fine.

What I don't understand is, withContext is still run on Dispatchers.IO. From the launch block, it may seem like non-blocking, but if Dispatchers.IO only has 1 thread and the withContext block is run on that thread, then that thread is still blocked isn't it?

I also learned Dispatchers.IO actually has virtually infinite threads, it just create a new one when needed. So the withContext actually is not blocking, but if this is true, why do we need the withContext block? The first code won't have any problem if Dispatchers.IO can create thread when needed thus will never be blocked, right?

Jeffrey Chen
  • 1,777
  • 1
  • 18
  • 29

2 Answers2

6

Yes, this is a bug with the warning. Lint can't detect what Dispatcher the scope is using, and I suppose they just assume that you're using a scope whose context uses Dispatchers.Main, since that is the most common.

Your CoroutineScope (pseudo-)constructor has a context with Dispatchers.IO, so launch inherits that context if it doesn't modify it, so your launched coroutine also uses Dispatchers.IO. So, your withContext block is redundant.

Workaround would be to specify the dispatcher when launching:

CoroutineScope(Job()).launch(Dispatchers.IO) {
    val fos = openFileOutput(fileName, MODE_PRIVATE)
    val oos = ObjectOutputStream(fos)
    oos.writeObject(myObj)
    oos.close()
}

Also, your statement:

So basically most Coroutine only has 1 thread, if it is blocked, that coroutine will have no thread to do other job.

is misleading. A coroutine doesn't have threads, a Dispatcher does. And some Dispatchers have many threads.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
4

It seems there's indeed a bug in Android Studio. The following code shows no warnings for me:

CoroutineScope(Dispatchers.IO).launch(Dispatchers.IO) {
    val fos = context.openFileOutput("", Context.MODE_PRIVATE)
    val oos = ObjectOutputStream(fos)
    oos.writeObject(myObj)
    oos.close()
}

You should also know that between this code and the two you shared, there's is virtually no difference in behavior. In all three cases the code will be executed on the IO thread.

  • CoroutineScope(context)
  • launch(context)
  • withContext(context

All these methods just specify the coroutine context. By default launch uses the coroutine scope context, but you can change that either as I did above or by using withContext.

So basically most Coroutine only has 1 thread, if it is blocked, that coroutine will have no thread to do other job.

Dispatchers.IO actually defaults to 64 threads.

Nicolas
  • 6,611
  • 3
  • 29
  • 73