Каналы — это способ передавать поток данных между корутинами.
В этом докладе пойдет речь об опыте применения корутин и каналов в нашем новом проекте “Маруся”:
- Почему мы решились использовать каналы, несмотря на экспериментальный статус, и были ли альтернативы?
- Как использовали каналы мы, и как использовать правильно?
- А также о том, как пришлось написать пару операторов из Rx, и чем их можно заменить?
3. fun main() {
GlobalScope. launch {
delay(1000L)
println("World!")
}
println("Hello,")
Thread.sleep(2000L)
}
Что такое корутины
3
4. • “О, клево! Наверное, будет очень классно!”
• “Залезаешь в новую технологию и просто собираешь камни лицом”
• “Баги в самих корутинах и непонятно, бага или фича”
• “Нереально сложно. Просто дико сложно. Не лежит рядом по
сравнению с эрикс-фигикс”
Впечатления команды
4
5. • “API корутин содержит сложности, но с ними меньше проблем, чем с
традиционными подходами к многопоточности”
• “Подход со structured concurrency упрощает жизнь, в частности Android-
разработчикам”
• “API охренительное очень мощное, поэтому сложное”
• “Нет колбэк хэла, просто, быстро. Гораздо удобнее, чем экзекьюторы”
Впечатления команды
5
6. • Круто, но есть подводные камни
• Обработка ошибок
• Скоупы
• Баги в самих корутинах
• В простые проекты заходит легко
Впечатления команды
6
8. • Последовательная обработка поступающих данных
• В разных потоках, несколькими потребителями
• Например:
○ аудио данные от микрофона
○ event loop
○ очередь команд
Какую проблему решаем
8
9. • Сервер управляет приложением
• Приложение реализует набор команд:
○ Проиграть звук
○ Показать текст, картинку и т.д.
○ Запустить запись звука
○ И другие
• Можно внедрять новые фичи на бэкенде
Очередь команд в Марусе
9
11. Несмотря на экспериментальный статус
• Альтернативы не знают о корутинах
• Потоки != корутины
• Своё писать дорого
Почему каналы
11
12. val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x * x)
}
repeat(5) { println(channel.receive()) }
println("Done!")
Что такое каналы
12
13. • Передача данных между корутинами
• Примитив синхронизации
• Потокобезопасная очередь
• Горячий источник данных
Что такое каналы
13
14. • Сommunicating sequential processes, CSP
• Избегаем разделяемой памяти
• Иммутабельные данные
Взаимодействующие
последовательные процессы
14
15. Как устроены каналы
• Несут затраты на синхронизацию
• Никита Коваль — Как устроены каналы в корутинах в Kotlin
https://www.youtube.com/watch?v=XHMXyYlQ8Eg
15
16. • Какой тип канала выбрать?
○ Channel
■ рандеву
■ буфер
■ связный список
■ conflated
○ Broadcast
○ ConflatedBroadcast
• Аналоги для subject из RxJava
Сложности
16
17. • Подписаться на канал и не закрыть его
Неправильное использование
val channel = Channel<String>(1)
GlobalScope.launch {
channel.consumeEach { println("Consumer: $it") }
println("Consumer finished")
}
GlobalScope.launch {
channel.send("Data")
// channel.close()
println("Producer finished")
}
17
18. • Открыть подписку и не потреблять данные
Неправильное использование
val broadcast = BroadcastChannel<String>(1)
GlobalScope.launch {
val receive : ReceiveChannel<String> = broadcast.openSubscription()
// receive.consumeEach { println("Consumer: $it") }
println("Consumer finished")
}
GlobalScope.launch {
delay(500)
broadcast.send( "Data1")
broadcast.send( "Data2")
broadcast.close()
println("Producer finished")
}
18
19. Неправильное использование
val broadcast = BroadcastChannel<Recorder.RecordData>( BUFFER_CAPACITY)
while (recorder.isInRecordingState()) {
val recordData = recorder.readNext()
broadcast.send(recordData)
// val success = broadcast.offer(recordData)
// if (!success) {
// // ...
// }
}
• Не учитывать возможное переполнение
19
20. Неправильное использование
• Использование там, где нет необходимости
private val channel = BroadcastChannel<ActivityResult>( 1)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
{
super.onActivityResult(requestCode, resultCode, data)
channel.offer(ActivityResult(requestCode, resultCode))
}
class FragmentViewModel( private val channel: BroadcastChannel<ActivityResult>)
: ViewModelScope() {
fun listenResult() {
launch {channel.consumeEach { /* process result */ } }
}
}
20
21. • Обработка “горячего” потока данных: микрофон, входящее сетевое
соединение и т.д.
• Сведение нескольких потоков данных в один
• Безопасная работа с состоянием
Правильное использование
21
22. • Были, но deprecated
• Были не все, приходилось писать самим
Операторы
22
27. • Модель акторов
• Актор:
○ корутина
○ состояние
○ “почтовый ящик”
Actor
27
28. Actor
val c = actor {
// initialize actor's state
for (msg in channel) {
// process message here
}
}
// send messages to the actor
c.send(...)
...
// stop the actor when it is no longer needed
c.close()
28
29. CombineLatest
fun <F, S, R> ReceiveChannel< F>.combineLatest(
other: ReceiveChannel< S>, context: CoroutineContext = Dispatchers. Unconfined,
transform: (a: F, b: S) -> R): ReceiveChannel< R> {
class StatePart(val argNum: Int, val arg1: F?, val arg2: S?)
return GlobalScope. produce(context, onCompletion = consumesAll(this, other)) {
val actor = actor<StatePart> {
var first: F? = null; var second: S? = null
var isFirstSet = false; var isSecondSet = false
for (next in this) {
if (next.argNum == 1) { isFirstSet = true; first = next. arg1 }
else { isSecondSet = true; second = next. arg2 }
if (isFirstSet && isSecondSet) {
@Suppress("UNCHECKED_CAST")
send(transform(first as F, second as S))
}
}
}
launch { this@combineLatest .consumeEach { value -> actor.send(StatePart( 1, value, null)) } }
launch { other.consumeEach { value -> actor.send(StatePart( 2, null, value)) } }
}
} 29
35. • Блог Романа Елизарова medium.com/@elizarov
• kotlinlang.org
• github.com/Kotlin/kotlinx.coroutines
Что читать
35
36. • Корутины:
○ Лаконичный код без коллбэк хелла
○ Мощный механизм областей видимости
○ Сложность, неочевидные моменты, другая парадигма
• Каналы:
○ Область применения ограничена — горячий источник
○ Примитив синхронизации
○ Также есть сложности и неочевидные моменты
○ Экспериментальны?
○ Следить за Flow
Резюме
36
37. • Кирилл Филимонов
• Александр Шаталов
• Владимир Ширяев
• Александр Королёв
• Виктор Филлипов
• Рустам Ситдиков
Благодарности
37