Производить<тип> против канала<тип> ()
Пытаться понять каналы. Я хочу направить андроид 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.
что имеет смысл. Я знаю, что мы можем использовать suspendCoroutine
с resume
за один выстрел callback
-замена. Это, однако, ситуация слушателя / потока. Я не хочу, чтобы сопрограмма завершена
1 ответ
С помощью produce
, вы вводите область действия на свой канал. Это означает, что код, который производит элементы, которые передаются по каналу, может быть отменен.
Это также означает, что время жизни вашего канала начинается в начале лямбда produce
и заканчивается, когда заканчивается эта лямбда.
В вашем примере лямбда вашего produce
звонок почти заканчивается немедленно, что означает, что ваш канал закрыт почти сразу.
Измените свой код на что-то вроде этого:
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<Nothing> { ... }
чтобы сделать его приостановить "навсегда".
Обновление: Использование 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(...)
}
}
}