Производить<тип> против канала<тип> ()

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