Search code examples
androidkotlinblockingqueuekotlinx.coroutines

Coroutines Android 4.1: subsequent launches not working after blocking queue


If I run a launch that blocks with a blocking queue inside, no other launch after that will run. This only happens on Android 4.1, other devices I tested with Android 6.0.1 and 7.0 work just fine. Here's an example:

class MainActivity : AppCompatActivity() {

    private val blockingQueue = ArrayBlockingQueue<String>(10)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        launch {
            Log.d(javaClass.simpleName, "TEST 1")
        }
        launch {
            blockingQueue.take().run {
                Log.d(javaClass.simpleName, "TEST 2")
            }
        }
        launch {
            Log.d(javaClass.simpleName, "TEST 3")
        }
    }
}

Output:

05-15 12:09:39.707 4337-4361/org.testcoroutines D/StandaloneCoroutine: TEST 1

TEST 3 is never logged. However if I replace the "blocking" launch with thread, it works.

If I unblock the queue by putting an element on it, the rest of the launches now do run.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    launch {
        Log.d(javaClass.simpleName, "TEST 1")
    }
    launch {
        blockingQueue.take().run {
            Log.d(javaClass.simpleName, "TEST 2 $this")
        }
    }
    launch {
        Log.d(javaClass.simpleName, "TEST 3")
    }
    thread {
        Thread.sleep(2000)
        Log.d(javaClass.simpleName, "TEST WAKE UP")
        blockingQueue.put("WAKE UP!")
    }
}

Output:

05-15 12:10:33.367 4471-4492/org.testcoroutines D/StandaloneCoroutine: TEST 1
05-15 12:10:35.387 4471-4493/org.testcoroutines D/MainActivity: TEST WAKE UP
05-15 12:10:35.387 4471-4492/org.testcoroutines D/String: TEST 2 WAKE UP!
05-15 12:10:35.387 4471-4492/org.testcoroutines D/StandaloneCoroutine: TEST 3

Any ideas why this might be happening and how to fix it?


Solution

  • ArrayBlockingQueue.take operation is blocking. It blocks the invoker thread and does not allow it to be used for anything else. On "small" devices there might be just one background thread in the default thread pool and when you block this thread nothing else useful can happen. Coroutines are designed to work with non-blocking (asynchronous) APIs that do not block threads.

    With coroutines you should use channels which serve roughly the same purpose for coroutines as blocking queues serve for threads. Channels do not block the invoker thread, but suspend invoking coroutine, thus allowing to use the same thread for multiple running coroutines.

    You can find more information about channels in the Guide to kotlinx.coroutines by example.