Корутины

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

Самый распространенный способ выполнять задачи конкурентно — использовать потоки: независимые пути выполнения, которыми управляет операционная система. Однако потоки относительно тяжелые, и создание большого количества потоков может привести к проблемам с производительностью.

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

Большинство возможностей корутин предоставляет библиотека kotlinx.coroutines. Она включает инструменты для запуска корутин, управления конкурентным выполнением, работы с асинхронными потоками данных и других задач.

Если вы только знакомитесь с корутинами в Kotlin, начните с руководства Основы корутин, прежде чем переходить к более сложным темам. В нем на простых примерах представлены ключевые понятия: приостанавливающие функции, билдеры корутин и структурированная конкурентность.

Посмотрите приложение KotlinConf как пример проекта, в котором корутины используются на практике.

Концепции корутин

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

Приостанавливающие функции и билдеры корутин

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

Чтобы запускать новые корутины, используйте билдеры корутин, например .launch() и .async(). Эти билдеры являются функциями-расширениями для CoroutineScope, который определяет жизненный цикл корутины и предоставляет контекст корутины.

Подробнее об этих билдерах можно узнать в разделах Основы корутин и Составление функций приостановки.

Контекст и поведение корутин

Запуск корутины из CoroutineScope создает контекст, управляющий ее выполнением. Функции-билдеры, такие как .launch() и .async(), автоматически создают набор элементов, которые определяют поведение корутины:

  • Интерфейс Job отслеживает жизненный цикл корутины и обеспечивает структурированную конкурентность.
  • CoroutineDispatcher управляет тем, где выполняется корутина: например, в фоновом потоке или в главном потоке UI-приложения.
  • CoroutineExceptionHandler обрабатывает неперехваченные исключения.

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

Асинхронный поток и разделяемое изменяемое состояние

Kotlin предоставляет несколько способов коммуникации между корутинами. Выберите один из следующих вариантов в зависимости от того, как нужно передавать значения между корутинами:

  • Flow создает значения только тогда, когда корутина активно их собирает.
  • Channel позволяет нескольким корутинам отправлять и получать значения, при этом каждое значение доставляется ровно одной корутине.
  • SharedFlow непрерывно передает каждое значение всем активным собирающим корутинам.

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

Подробнее см. в разделах Асинхронный поток, Каналы и в руководстве Корутины и каналы.

Что дальше