首页 > 解决方案 > 协程 - 带有连接的 Dispatchers.Main.immediate 在 runBlocking 内死锁

问题描述

分解一个在 Android 上挂起主线程并使用协程执行并发处理的简单案例,以下代码仅打印Launched并且runBlocking永远不会完成:

runBlocking {
      val ioJob = launch(Dispatchers.IO) {
        delay(1000)
      }
      val mainJob = launch(Dispatchers.Main.immediate) {
        Log.d("Routine", "Launched")
        ioJob.join()
        Log.d("Routine", "Joined")
      }

      listOf(mainJob, ioJob).joinAll()
    }

当然,如果我们替换Dispatchers.Main.immediate一切Dispatchers.IO都很好,但是我的一些并发处理应该在 main 上运行。正如预期的那样,使用Dispatchers.Main不会记录任何内容。似乎一旦join在根中执行a ,runBlocking它就会使任何被分派到 main 的挂起的东西瘫痪。

值得注意的是,基于CountDownLatch线程的方法效果很好:

    val latchMain = CountDownLatch(1)
    val primaryLatch = CountDownLatch(2)

    val ioExecutor = Executors.newCachedThreadPool(Executors.defaultThreadFactory())

    log("Execute IO")
    ioExecutor.execute(
        Runnable {
          log("Delay IO")
          Thread.sleep(1000)
          log("Countdown Main")
          latchMain.countDown()
          Thread.sleep(3000)
          primaryLatch.countDown()
        })

    log("Execute Main")
    Runnable {
      log("Await Main")
      latchMain.await()
      log("Countdown Primary")
      primaryLatch.countDown()
    }.run()

    log("Await Primary")
    primaryLatch.await()
    log("Continue")

    stepTracker.endTracking()
    return stepTracker.stepGraphTrace
  }

  private fun log(msg: String) = Log.i("Routine", "[${Thread.currentThread().name}] $msg")

带输出:

2021-08-11 11:04:06.508 [main] Execute IO
2021-08-11 11:04:06.509 [main] Execute Main
2021-08-11 11:04:06.510 [main] Await Main
2021-08-11 11:04:06.510 [pool-25-thread-1] Delay IO
2021-08-11 11:04:07.512 [pool-25-thread-1] Countdown Main
2021-08-11 11:04:07.513 [main] Countdown Primary
2021-08-11 11:04:07.513 [main] Await Primary
2021-08-11 11:04:10.514 [main] Continue

有任何想法吗?即将就这个问题向 JetBrains 提交问题。

标签: androidkotlinkotlin-coroutines

解决方案


不回答原始问题,而是质疑问题本身;-)。

基于闩锁的解决方案“假装”为两个“作业”使用并发,但只有 io 作业可以。第二个 Runnable 只是包装要运行的代码,什么也不做。

这是做完全相同的事情,但更简单:

val latchMain = CountDownLatch(1)
val ioExecutor = Executors.newCachedThreadPool(Executors.defaultThreadFactory())

ioExecutor.execute(
    Runnable {
        Thread.sleep(1000)
        latchMain.countDown()
    }
)

latchMain.await()

基于“相同”协程的实现非常简单:

runBlocking {
    val ioJob = launch(Dispatchers.IO) {
        delay(1000)
    }

    ioJob.join()
}

或者对 io 作业使用相同的线程池:

runBlocking {
    val ioJob = launch(ioExecutor.asCoroutineDispatcher()) {
        delay(1000)
    }

    ioJob.join()
}

推荐阅读