取消消费者的工作后,关闭协程频道

时间:2018-11-21 14:27:07

标签: kotlin kotlinx.coroutines

我有一个使用协程渠道的简单生产者和消费者。这是一个愚蠢的版本:

class Producer {

  suspend fun start(): ReceiveChannel<String> {

    val channel = Channel<String>(Channel.UNLIMITED)

    // Asynchronous channel.send(it) from an object callback

    channel.invokeOnClose {
      // Channel is closed...
    }

    return channel
  }

}

class Consumer : CoroutineScope {

  private val producer = Producer()

  private val job = Job()
  override val coroutineContext = job + Dispatchers.Default

  fun start() {
    launch {
      val channel = producer.start()

      for (currentValue in channel) {
        // use currentValue
      }
    }
  }

  fun stop() {
    job.cancel()
  }

}

Producer创建一个通道,然后用异步作业中的值填充它。 Consumer对其进行迭代并使用这些值。

我的期望是,当我从使用者调用job.cancel()时,通道迭代器将抛出并且通道将关闭。永远不会调用invokeOnClose回调。

我可以维护对Consumer中的频道的引用,并执行channel.close()。我想知道是否有更聪明的解决方案。也许是迭代频道值的另一种方法?谢谢吗?

修改

好像使用

launch {
    val channel = producer.start()

    channel.consumeEach { currentValue ->
    // use currentValue
    }
}

可以解决问题。但是consumeEach()被标记为过时。

1 个答案:

答案 0 :(得分:0)

您期望job.cancel()会传播到您的生产者,但是Producer实际上与任何东西都不相关。将函数标记为suspend并不能使其成为协程。

这是一种通过结构化并发解决此问题的方法:

class Producer: CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = Job() + Dispatchers.Default

    suspend fun start() = produce<String> {
        channel.send("A")

        channel.invokeOnClose {
            println("Closed")
        }
    }
}

现在您的Producer知道CoroutineScope

由于我们使用的是produce,因此您无需像以前那样初始化频道。