【问题标题】:produce<Type> vs Channel<Type>()生产<Type> vs Channel<Type>()
【发布时间】:2019-10-09 00:15:57
【问题描述】:

试图了解渠道。我想对 android BluetoothLeScanner 进行通道化。为什么会这样:

fun startScan(filters: List<ScanFilter>, settings: ScanSettings = defaultSettings): ReceiveChannel<ScanResult?> {
    val channel = Channel<ScanResult>()
    scanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            channel.offer(result)
        }
    }
    scanner.startScan(filters, settings, scanCallback)

    return channel
}

但不是这个:

fun startScan(scope: CoroutineScope, filters: List<ScanFilter>, settings: ScanSettings = defaultSettings): ReceiveChannel<ScanResult?> = scope.produce {
    scanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            offer(result)
        }
    }
    scanner.startScan(filters, settings, scanCallback)
}

它告诉我Channel was closed 第一次想呼叫offer

EDIT1: 根据文档:The channel is closed when the coroutine completes. 这是有道理的。我知道我们可以使用suspendCoroutineresume 一次性使用callback-replacement。然而,这是一个监听器/流的情况。我不希望协程完成

【问题讨论】:

  • 确保您的协程范围的生命周期与蓝牙扫描仪的范围相匹配。如果您有一个活动绑定范围但不适合,请创建一个不同的范围。
  • 我玩过瞄准镜,没什么区别。我想问题是在scanner.startscan之后产生返回,这意味着它已经完成了
  • produce 调用立即返回,它返回的值是您需要从中消费数据的ReceiveChannel。但是,是的,produce 块应该是一个无限循环,将数据推送到通道中。在您的情况下,produce 块立即完成。所以你的第一个例子更适合你的基于回调的方法。
  • produce 在这种情况下不是您想要的。对于这样的用例,您需要一个合适的渠道。

标签: kotlin coroutine kotlin-coroutines kotlinx.coroutines.channels


【解决方案1】:

使用produce,您可以为您的频道引入范围。这意味着,可以取消生成通过通道流式传输的项目的代码。

这也意味着您的 Channel 的生命周期从 produce 的 lambda 的开头开始,并在此 lambda 结束时结束。

在您的示例中,您的 produce 调用的 lambda 几乎立即结束,这意味着您的频道几乎立即关闭。

把你的代码改成这样:

fun CoroutineScope.startScan(filters: List<ScanFilter>, settings: ScanSettings = defaultSettings): ReceiveChannel<ScanResult?> = produce {
    scanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            offer(result)
        }
    }
    scanner.startScan(filters, settings, scanCallback)

    // now suspend this lambda forever (until its scope is canceled)
    suspendCancellableCoroutine<Nothing> { cont ->
        cont.invokeOnCancellation {
            scanner.stopScan(...)
        }
    }
}

...
val channel = scope.startScan(filter)
...
...
scope.cancel() // cancels the channel and stops the scanner.

我添加了 suspendCancellableCoroutine&lt;Nothing&gt; { ... } 行使其“永远”暂停。

更新:使用produce 并以结构化方式处理错误(允许结构化并发):

fun CoroutineScope.startScan(filters: List<ScanFilter>, settings: ScanSettings = defaultSettings): ReceiveChannel<ScanResult?> = produce {
    // Suspend this lambda forever (until its scope is canceled)
    suspendCancellableCoroutine<Nothing> { cont ->
        val scanCallback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                offer(result)
            }
            override fun onScanFailed(errorCode: Int) {
                cont.resumeWithException(MyScanException(errorCode))
            }
        }
        scanner.startScan(filters, settings, scanCallback)

        cont.invokeOnCancellation {
            scanner.stopScan(...)
        }
    }
}

【讨论】:

  • 因此,虽然这似乎是“如何保持生产块提供的协程活动”的解决方案,但我认为 Marko 和 gMale 是正确的,而生产根本不是这种情况的正确工具。还是投个赞成票吧!
  • 我认为使用produce 是一个不错的选择。您还可以使用生产以结构化的方式处理错误。我更新了上面的答案,以展示一个以结构化方式处理错误的示例。
猜你喜欢
  • 1970-01-01
  • 2020-08-06
  • 2021-03-04
  • 2012-08-29
  • 1970-01-01
  • 1970-01-01
  • 2012-03-07
  • 1970-01-01
相关资源
最近更新 更多