androidkotlinkotlin-coroutines

Why does Android prohibit running IO-performing coroutines from the main thread?


Context and prior understanding:


But on Android with Kotlin, some suspend functions provided by libraries, when awaited from the main thread (using lifecycleScope.launch {}), will cause the runtime to crash. One example of that are suspend functions defined using Room (the SQLite3 wrapper). I would expect the blocking code to be run on another thread, and the result to be yielded back to a main thread's async runtime, but that's apparently not what's happening. To avoid the exception, I have to use withContext(Dispatchers.IO) {}, but this function is supposed to be used to schedule actually blocking code on a worker thread, right? From my bad understanding, it seems like Android (or Kotlin) conflates some concepts I'm used to. Or maybe, Room's suspend functions are actually implemented as blocking functions whose coroutine resolves immediately, and the suspend qualifier is just there for looks?

Another weird detail is that running these suspend functions from the main thread doesn't always cause an exception, as if there was some non-determinism in the thread pool used to schedule the coroutines.


Solution

  • IO isn't allowed on the main thread at all. That's why an IO coroutine isn't allowed on the main thread. This is for several reasons:

    1)The central message loop. If control isn't returned to the central message loop, the entire UI will be unresponsive. As such, we need to ensure that the main thread is not waiting for significant amounts of time. Coroutines do not solve this problem, as coroutines just allow a thread to be reused, it would not allow the thread to go back to the message loop. And changing the message loop so it could do that would cause major problems, as part of the message loop's purpose is sycnhronizing events.

    This isn't a problem in JS because the entire JS engine runs on its own thread, separate from the browser's UI. So even if there is a message loop, it won't hold up the UI.

    2)Lifecycle functions. This is similar to 1, but a special case of it. Lifecycle functions like onCreate, onPause, etc are timed. If they go to long, the app is unresponsive. As such the OS will kill them under the assumption that the app is broken. This is what causes ANRs. This watchdog timer behavior is very common in hardware and embedded worlds.

    3)The OS wasn't written for kotlin or other languages with coroutines or await mechanics. It was written for Java. In that world, coroutines don't exist so there was no reason to try to allow them. But moving out of that world would take major changes to the framework for minor gains (that would still probably be bad practice) so I don't expect them to do it.

    As for your claims that you can sometimes call suspend functions on the main thread- you can't, and the compiler will not compile the code if you try. You're missing something there that calls it from an allowed context.