производить <Type>против канала <Type>() - PullRequest
0 голосов
/ 22 мая 2019

Пытается понять каналы. Я хочу направить андроид 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.

РЕДАКТИРОВАТЬ1: Согласно документам: The channel is closed when the coroutine completes., что имеет смысл. Я знаю, что мы можем использовать suspendCoroutine с resume для замены одним выстрелом callback. Это, однако, ситуация слушателя / потока. Я не хочу, чтобы сопрограмма завершилась

1 Ответ

1 голос
/ 24 мая 2019

Используя 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(...)
        }
    }
}
...