Корутины

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

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

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

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

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

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

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

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

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

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

И для добавления новой библиотеки нажмем на знак плюса и в контекстном меню выберем пункт From Maven...

После этого нам откроется окно для добавления библиотеки через Maven. В этом окне в поле ввода введем название нужной нам библиотеки - kotlinx-coroutines-core-jvm и нажмем на кнопку поиска. Если соответствующая библиотека найдена, то нам отобразится выпадающий список с результатами

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

Выберем из него последнюю версию, которая называется наподобие org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4 - в данном случае используется версия 1.6.4, но конкретный номер версии может отличаться.

Отметим все необходимые флажки и нажмем на кнопку OK

Добавление библиотеки kotlinx-coroutines-core-jvm через Maven в проект на Kotlin

После установки библиотеки мы сможем найти ее файл в списке библиотек

библиотека kotlinx-coroutines-core-jvm в проекте на Kotlin

В качестве альтернативы мы могли бы вручную подключить нужную библиотеку из локального хранилища. Так, на Windows это будет папка C:\Users\[Имя_пользователя]\AppData\Roaming\JetBrains\IdeaIC[номер_версии]\plugins\Kotlin\kotlinc\lib

Далее в этой папке выберем библиотеку kotlinx-coroutines-core-jvm.jar и нажмем на OK для ее добавления:

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

Определение 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() может применяться только в функции с модификатором suspend, коей является функция 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().

Обратите внимание, что в примере выше в конце функции main вызывается функция println(), которая выводит строку на консоль. Если мы ее удалим, то мы столкнемся с ошибкой - функция main должна возвращать значение Unit. В этом случае мы можем либо явным образом возвратить значение Unit:

import kotlinx.coroutines.*

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

Либо можно типизировать функцию coroutineScope типом Unit:

import kotlinx.coroutines.*

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

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

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

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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850