Channels

https://kotlinlang.org/docs/channels.html

Channel

Каналы предназначены для безопасной и эффективной передачи данных между корутинами. Позволяют создавать системы обмена данными, где одна корутина может передавать значения другой корутине без блокировки потоков.

Асинхронность: Корутины могут отправлять данные в канал и получать их, не блокируя потоки. Это позволяет корутинам эффективно взаимодействовать друг с другом.

Буферизация: Каналы могут быть буферизованными или небуферизованными, что контролирует поведение при отправке и получении данных.

Закрытие: Канал можно закрыть, чтобы сообщить другим корутинам, что данные больше не будут отправляться.

suspend fun main() = coroutineScope {
    val channel = Channel<String>()
    launch {
        val users = listOf("Tom", "Bob", "Sam")
        for (user in users) {
            println("Sending $user")
            channel.send(user)
        }
    }
    repeat(3) {
        val user = channel.receive()
        println("Received: $user")
    }
    println("End")
}

// Sending Tom
// Received: Tom
// Sending Bob
// Received: Bob
// Sending Sam
// Received: Sam
// End

Rendezvous Channel

Небуферизованный канал не может хранить элементы — каждая операция отправки приостанавливается до тех пор, пока другая корутина не выполнит соответствующую операцию получения.

val channel = Channel<Int>() // небуферизованный канал

Buffered Channel

Буферизованные каналы могут хранить несколько элементов до того, как отправка или получение будут заблокированы. Размер буфера задается при создании канала.

val channel = Channel<Int>(capacity = 5) // буферизованный канал на 5 элементов

Conflated Channel

Канал с потоками данных сохраняет только последнее значение. При отправке новых данных предыдущие теряются.

val channel = Channel<Int>(Channel.CONFLATED)

send

Отправляет значение в канал. Если канал имеет место для хранения данных (например, буферизованный канал), значение будет добавлено. Если канал не имеет буфера или он полный, корутина, выполняющая send, приостанавливается, пока в канал не станет доступно место для отправки.

channel.send(value)

receive

Получает значение из канала. Если канал пустой, корутина приостанавливается до тех пор, пока в канал не будет отправлено значение.

val value = channel.receive()

offer

Это не блокирующая альтернатива send. Она пытается немедленно отправить значение в канал. Если канал полон (например, буферизованный канал заполнен или RendezvousChannel без готового получателя), операция не будет приостанавливаться, и вызов вернет false.

val success = channel.offer(value)
if (!success) {
    println("Канал полон, не удалось отправить значение")
}

poll

Это не блокирующая альтернатива receive. Она пытается немедленно получить значение из канала. Если канал пуст, вызов вернет null (или значение по умолчанию, если указано).

val value = channel.poll()
if (value == null) {
    println("Канал пуст")
}

close

Закрывает канал, указывая на то, что больше данных отправлено не будет. После вызова close() ни одна корутина не сможет отправить данные через этот канал, но уже отправленные данные могут быть получены.

channel.close() // закрыть канал

isClosedForSend & isClosedForReceive

Используются для проверки состояния канала.

if (channel.isClosedForSend) {
    println("Канал закрыт для отправки")
}

if (channel.isClosedForReceive) {
    println("Канал закрыт для получения")
}

consumeEach

Упрощенный способ получать данные из канала в виде цикла. Эта функция завершает цикл, когда канал закрыт, а все данные получены.

channel.consumeEach { value ->
    println("Получено: $value")
}

invokeOnClose

Позволяет задать обработчик, который будет вызван при закрытии канала. Это полезно для выполнения операций очистки или другой логики при завершении работы с каналом.

channel.invokeOnClose { 
    println("Канал закрыт")
}

receiveAsFlow

Преобразовать канал в Flow.

val flow = channel.receiveAsFlow()

ReceiveChannel

Используется для чтения данных из канала. Представляет собой интерфейс.

val channel = Channel<Int>()
    
// Использование ReceiveChannel для получения данных
val receiveChannel: ReceiveChannel<Int> = channel

// Чтение данных из канала
for (value in receiveChannel) {
    println("Received: $value")
}

produce

Используется для создания и запуска корутины, которая отправляет данные в канал. Этот метод позволяет вам создать канал и одновременно запустить корутину, которая будет наполнять этот канал данными. Он удобен для создания потоков данных, которые могут быть обработаны другими корутинами.

fun CoroutineScope.getUsers(): ReceiveChannel<String> = produce {
    val users = listOf("Tom", "Bob", "Sam")
    for (user in users) {
        send(user)
    }
}

Kotlin Channels. Вопросы на собесе
  1. Какую функцию выполняют каналы?
  1. Какие типы Channel доступны в Kotlin и как они отличаются?
  1. Что такое BufferedChannel и как он отличается от обычного Channel?
  1. Как использовать Channel для реализации паттернов Producer-Consumer?
  1. Как работают операции send, receive, offer, и poll в Channel?