Корутины

Введение в корутины

Последнее обновление: 18.06.2021

В последнее время поддержка асинхронности и параллельных вычислений стала неотъемлимой чертой многих языков программирования. И Kotlin не является исключением. Зачем нужны асинхронность и параллельные вычисления? Параллельные вычисления позволяют выполнять несколько задач одновременно, а асинхронность позволяет не блокировать основной ход приложения во время выполнения задачи, которая занимает продолжительное время. Например, мы создаем графическое приложение для десктопа или мобильного устройства. И нам надо по нажатию на кнопку отправлять запрос к интернет-ресурсу. Однако подобный запрос может занять довольно много время. И чтобы приложение не зависало на период отправки запроса, подобные запросы к интернет-ресурсам следует отправлять асинхронно. При асинхронных запросах пользователь не ждет пока придет ответ от интернет-ресурса, а продолжает работу с приложением, а при получении ответа получит соответствующее уведомление.

В языке Kotlin поддержка асинхронности и параллельных вычислений воплощена в виде корутин (coroutine). По сути корутина представляет блок кода, который может выполняться параллельно с остальным кодом. А базовая функциональность, связанная с корутинами, сосредоточена в библиотеке kotlinx.coroutines.

Рассмотрим определение и применение корутины на простейшем примере.

Добавление kotlinx.coroutines

Прежде всего стоит отметить, что функциональность корутин (библиотека kotlinx.coroutines) по умолчанию не включена в проект. И нам ее надо добавить. Если мы создаем проект консольного приложения в IntelliJ IDEA, то мы можем добавить соответствующую библиотеку в проект. Для этого в меню File перейдем к пункту Project Structure..

Добавление в проект kotlinx.coroutines

Далее на вкладке "Project Settings" перейдем к пункту Libraries. В центральном поле отобразятся библиотеки, добавленные в проект. И для добавления новой библиотеки нажмем на знак плюса:

Add to console project kotlinx.coroutines

Нас перебросит к папке lib, которая содержит устанавливаемые по умолчанию библиотеки Kotlin. Выберем среди них библиотеку kotlinx-coroutines-core.jar и нажмем на OK для ее добавления:

Добавление в проект kotlinx-coroutines-core.jar

После добавления в проекте в узле External Libraries / Kotlin Java Runtime (2) мы увидим добавленную библиотеку:

Add to console Kotlin project kotlinx-coroutines-core.jar

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

Определение suspend-функции

Сначала рассмотрим пример, который не использует корутины:

import kotlinx.coroutines.*

suspend fun main(){
    for(i in 0..5){
        delay(400L)
        println(i)
    }

    println("Hello Coroutines")
}

Здесь в функции main перебираем последовательность от 0 до 5 и выводит текущий элемент последовательности на консоль. Для имитации продолжительной работы внутри цикла вызываем специальную функцию delay() из пакета kotlinx.coroutines. В эту функцию передается количество миллисекунд, на которое выполняется задержка. Передаваемое значение должно иметь тип Long. То есть здесь функция будет выполнять задержку в 400 миллисекунд перед выводом на консоль текущего элемента последовательности.

После выполнения работы цикла выводим на консоль строку "Hello Coroutines".

И чтобы использовать внутри функции main функцию delay(), функция main предваряется модификатром suspend. Модификатор suspend определяет функцию, которая может приостановить свое выполнение и возобновить его через некоторый период времени.

Сама функция delay() то же является подобной функцией, которая определена с модификатором suspend. А любая функция с модификатором suspend может вызывать только либо из другой функции, которая тоже имеет модификатор suspend, либо из корутины.

Если мы запустим приложение, то мы увидим следующий консольный вывод:

0
1
2
3
4
5
Hello Coroutines

Здесь мы видим, что строка "Hello Coroutines" выводится после выполнения цикла. Но вместо цикла у нас могла бы быть более содержательная, но и более продолжительная работа, например, обращение к интернет-ресурсу, к удаленой базе данных, какие-то операции с файлами и т.д. И в этом случае все определенные после этой работы действия ожидали бы завершения этой продолжительной работы, как в данном случае строка "Hello Coroutines" ждет завершения цикла.

Определение корутины

Теперь вынесем продолжительную работу - то есть цикл в корутину:

import kotlinx.coroutines.*

suspend fun main() = coroutineScope{
    launch{
            for(i in 0..5){
            delay(400L)
            println(i)
        }
    }

    println("Hello Coroutines")
}

Прежде всего для определения и выполнения корутины нам надо определить для нее контекст, так как корутина может вызываться только в контексте корутины (coroutine scope). Для этого применяется функция coroutineScope() - создает контекст корутины. Кроме того, эта функция ожидает выполнения всех определенных внутри нее корутин. Стоит отметить, что coroutineScope() может применяться только в функции с модификатором, коей является функция main.

Сама корутина определяется и запускается с помощью построителя корутин - функции launch. Она создает корутину в виде блока кода - в данном случае это:

{
	for(i in 0..5){
		delay(400L)
		println(i)
	}
}

и запускет эту корутину параллельно с остальным кодом. То есть данная корутина выполняется независимо от прочего кода, определенного в функции main.

В итоге при выполнении программы мы увидим несколько другой консольный вывод:

Hello Coroutines
0
1
2
3
4
5

Теперь строка "Hello Coroutines" не ожидает, пока завершится цикл, а выполняется параллельно с ним.

Вынесение кода корутин в отдельную функцию

Выше код корутины располагался непосредственно в функции main. Но также можно определить его в виде отдельной функции и вызывать в корутине эту функцию:

import kotlinx.coroutines.*

suspend fun main()= coroutineScope{
    launch{ doWork() }

    println("Hello Coroutines")
}
suspend fun doWork(){
    for(i in 0..5){
        println(i)
        delay(400L)
    }
}

В данном случае основной код корутины вынесен в функцию doWork(). Поскольку в этой функции применяется функция delay(), то doWork() определена с модификатором suspend. Сама корутина создается также с помощью функции launch(), которая вызывает функцию doWork().

Корутины и потоки

В ряде языков программирования есть такие структуры, которые позволяют использовать потоки. Однако между корутинами и потоками нет прямого соответствия. Корутина не привязана к конкретному потоку. Она может быть приостановить выполнение в одном потоке, а возобновить выполнение в другом.

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

Помощь сайту
WebMoney
  • P378451176208
  • Z280152397659
ЮMoney/Яндекс-Деньги
  • 410011174743222
PayPal
  • metanit22@mail.ru
Перевод на карту
  • Номер карты: 4048415020898850