Перейти к содержимому

Suspend fun kotlin что это

  • автор:

Составление функций приостановки

В этом разделе рассматриваются различные подходы к композиции функций приостановки.

Последовательно по умолчанию

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

suspend fun doSomethingUsefulOne(): Int < delay(1000L) // притворимся, что мы делаем что-то полезное здесь return 13 >suspend fun doSomethingUsefulTwo(): Int < delay(1000L) // притворимся, что мы тоже делаем что-то полезное здесь return 29 >

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

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

import kotlinx.coroutines.* import kotlin.system.* fun main() = runBlocking < val time = measureTimeMillis < val one = doSomethingUsefulOne() val two = doSomethingUsefulTwo() println("The answer is $") > println("Completed in $time ms") > suspend fun doSomethingUsefulOne(): Int < delay(1000L) // притворимся, что мы делаем что-то полезное здесь return 13 >suspend fun doSomethingUsefulTwo(): Int < delay(1000L) // притворимся, что мы тоже делаем что-то полезное здесь return 29 >

You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt). —>

Получается что-то вроде этого:

The answer is 42 Completed in 2017 ms 

Параллельное использование async

Что, если между вызовами doSomethingUsefulOne и doSomethingUsefulTwo нет зависимостей, и мы хотим получить ответ быстрее, выполняя оба одновременно? Здесь на помощь приходит async.

Концептуально async аналогична launch. Она запускает отдельную корутину, представляющую собой облегченный поток, который работает одновременно со всеми другими корутинами. Разница в том, что launch возвращает Job и не несет никакого результирующего значения, тогда как async возвращает Deferred — облегченное неблокирующее будущее значение, которое представляет собой обещание предоставить результат позже. Вы можете использовать .await() для отложенного значения, чтобы получить его окончательный результат, но Deferred также является Job , поэтому при необходимости вы можете отменить его.

import kotlinx.coroutines.* import kotlin.system.* fun main() = runBlocking  < val time = measureTimeMillis < val one = async < doSomethingUsefulOne() >val two = async < doSomethingUsefulTwo() >println("The answer is $") > println("Completed in $time ms") > suspend fun doSomethingUsefulOne(): Int < delay(1000L) // притворимся, что мы делаем что-то полезное здесь return 13 >suspend fun doSomethingUsefulTwo(): Int < delay(1000L) // притворимся, что мы тоже делаем что-то полезное здесь return 29 >

You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt). —>

Получается что-то вроде этого:

The answer is 42 Completed in 1017 ms 

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

Ленивый старт async

При желании async можно сделать ленивой, установив для параметра start значение CoroutineStart.LAZY. В этом режиме он запускает корутину только тогда, когда ее результат требуется для await или если вызывается функция start его Job . Запустите следующий пример:

import kotlinx.coroutines.* import kotlin.system.* fun main() = runBlocking  < val time = measureTimeMillis < val one = async(start = CoroutineStart.LAZY) < doSomethingUsefulOne() >val two = async(start = CoroutineStart.LAZY) < doSomethingUsefulTwo() >// some computation one.start() // start the first one two.start() // start the second one println("The answer is $") > println("Completed in $time ms") > suspend fun doSomethingUsefulOne(): Int < delay(1000L) // притворимся, что мы делаем что-то полезное здесь return 13 >suspend fun doSomethingUsefulTwo(): Int < delay(1000L) // притворимся, что мы тоже делаем что-то полезное здесь return 29 >

You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt). —>

Получается что-то вроде этого:

The answer is 42 Completed in 1017 ms 

Итак, здесь две корутины определены, но не выполняются, как в предыдущем примере, а программист получает контроль над тем, когда именно начинать выполнение, вызывая start. Сначала мы запускаем one , затем two , а затем ждем завершения отдельных корутин.

Обратите внимание, что если мы просто вызовем await в println без предварительного вызова start для отдельных корутин, это приведет к последовательному поведению, поскольку await запускает выполнение корутины и ожидает ее завершения, что не является предполагаемым «ленивым» вариантом использования. Вариант использования для async(start = CoroutineStart.LAZY) — это замена стандартной функции lazy в случаях, когда вычисление значения включает приостановку функций.

Функции в асинхронном стиле

Мы можем определить функции в асинхронном стиле, которые вызывают doSomethingUsefulOne и doSomethingUsefulTwo асинхронно, используя построитель async корутины с использованием ссылки на GlobalScope для отказа от структурированного параллелизма. Мы называем такие функции суффиксом «. Async», чтобы подчеркнуть тот факт, что они только запускают асинхронные вычисления, и для получения результата необходимо использовать полученное отложенное значение.

[GlobalScope] is a delicate API that can backfire in non-trivial ways, one of which will be explained > below, so you must explicitly opt-in into using `GlobalScope` with `@OptIn(DelicateCoroutinesApi::class)`. —>

GlobalScope — это деликатный API, который может иметь нетривиальные последствия, одно из которых будет объяснено ниже, поэтому вы должны явным образом согласиться на использование GlobalScope с помощью @OptIn(DelicateCoroutinesApi::class) .

// The result type of somethingUsefulOneAsync is Deferred @OptIn(DelicateCoroutinesApi::class) fun somethingUsefulOneAsync() = GlobalScope.async < doSomethingUsefulOne() >// The result type of somethingUsefulTwoAsync is Deferred @OptIn(DelicateCoroutinesApi::class) fun somethingUsefulTwoAsync() = GlobalScope.async

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

В следующем примере показано их использование вне корутины:

import kotlinx.coroutines.* import kotlin.system.* // обратите внимание, что в этом примере у нас нет `runBlocking` справа от `main` fun main() < val time = measureTimeMillis < // мы можем инициировать асинхронные действия вне корутины val one = somethingUsefulOneAsync() val two = somethingUsefulTwoAsync() // но ожидание результата должно включать либо приостановку, либо блокировку. // здесь мы используем `runBlocking < . >`, чтобы заблокировать основной поток в ожидании результата runBlocking < println("The answer is $") > > println("Completed in $time ms") > @OptIn(DelicateCoroutinesApi::class) fun somethingUsefulOneAsync() = GlobalScope.async < doSomethingUsefulOne() >@OptIn(DelicateCoroutinesApi::class) fun somethingUsefulTwoAsync() = GlobalScope.async < doSomethingUsefulTwo() >suspend fun doSomethingUsefulOne(): Int < delay(1000L) // притворимся, что мы делаем что-то полезное здесь return 13 >suspend fun doSomethingUsefulTwo(): Int < delay(1000L) // притворимся, что мы тоже делаем что-то полезное здесь return 29 >

You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt). —>

This programming style with async functions is provided here only for illustration, because it is a popular style > in other programming languages. Using this style with Kotlin coroutines is **strongly discouraged** for the > reasons explained below. —>

Этот стиль программирования с асинхронными функциями приведен здесь только для иллюстрации, так как он популярен в других языках программирования. Использование этого стиля с корутинами Kotlin настоятельно не рекомендуется по причинам, описанным ниже.

Рассмотрим, что произойдет, если между строкой val one = somethingUsefulOneAsync() и выражением one.await() в коде будет какая-то логическая ошибка, и программа выдаст исключение, а операция, которую выполняла программа, прервется. Обычно глобальный обработчик ошибок может перехватывать это исключение, регистрировать и сообщать об ошибке разработчикам, но в противном случае программа может продолжать выполнять другие операции. Однако здесь у нас somethingUsefulOneAsync все еще работает в фоновом режиме, даже несмотря на то, что операция, которая его инициировала, была прервана. Эта проблема не возникает при структурированном параллелизме, как показано в разделе ниже.

Структурированный параллелизм с async

Давайте возьмем пример Параллельное использование async и извлечем функцию, которая одновременно выполняет doSomethingUsefulOne и doSomethingUsefulTwo и возвращает сумму их результатов. Поскольку построитель async корутин определен как расширение CoroutineScope, нам нужно иметь его в области действия, и это то, что предоставляет функция coroutineScope:

suspend fun concurrentSum(): Int = coroutineScope < val one = async < doSomethingUsefulOne() >val two = async < doSomethingUsefulTwo() >one.await() + two.await() > 

Таким образом, если внутри кода функции concurrentSum что-то пойдет не так, и она выдаст исключение, все корутины, запущенные в ее области действия, будут отменены.

import kotlinx.coroutines.* import kotlin.system.* fun main() = runBlocking < val time = measureTimeMillis < println("The answer is $") > println("Completed in $time ms") > suspend fun concurrentSum(): Int = coroutineScope < val one = async < doSomethingUsefulOne() >val two = async < doSomethingUsefulTwo() >one.await() + two.await() > suspend fun doSomethingUsefulOne(): Int < delay(1000L) // притворимся, что мы делаем что-то полезное здесь return 13 >suspend fun doSomethingUsefulTwo(): Int < delay(1000L) // притворимся, что мы тоже делаем что-то полезное здесь return 29 >

You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt). —>

У нас все еще есть параллельное выполнение обеих операций, как видно из вывода вышеприведенной функции main :

The answer is 42 Completed in 1017 ms 

Отмена всегда распространяется через иерархию корутин:

import kotlinx.coroutines.* fun main() = runBlocking  < try < failedConcurrentSum() >catch(e: ArithmeticException) < println("Computation failed with ArithmeticException") >> suspend fun failedConcurrentSum(): Int = coroutineScope < val one = async < try < delay(Long.MAX_VALUE) // Эмулирует очень долгие вычисления 42 >finally < println("First child was cancelled") >> val two = async  < println("Second child throws an exception") throw ArithmeticException() >one.await() + two.await() > 

You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt). —>

Обратите внимание, как первый async и ожидающий родитель отменяются при сбое одного из дочерних элементов (а именно two ):

Second child throws an exception First child was cancelled Computation failed with ArithmeticException 

© 2015—2023 Open Source Community

Урок 3. Корутины. Suspend функции

В этом уроке подробно разберем как создать suspend функции. Также рассмотрим, можно ли блокировать поток, как корутина может потеряться и зачем нужно слово suspend.

В прошлом уроке мы подробно рассмотрели взаимодействие Continuation и suspend функции со стороны Continuation. Теперь посмотрим на это взаимодействие со стороны suspend функций.

Как создать suspend функцию

Сейчас мы будем рассматривать, как создать suspend функцию из асинхронного кода. Если же у вас есть какой-то синхронный метод и его надо сделать suspend, то там просто используется билдер withContext. Об этом мы поговорим, когда начнем тему билдеров.

Давайте создадим suspend функцию download, которую мы использовали ранее в примерах.

suspend fun download(url: String): File

Ключевое слово здесь — suspend. Это даст Котлину понять, что это suspend функция и она будет приостанавливать корутину. В конце урока я дам более подробное объяснение, зачем нужно использовать слово suspend. Но перед этим нам нужно понять, как такая функция работает.

Предположим, у нас есть некий NetworkService, который асинхронно умеет загружать файлы. И мы хотим обернуть его в suspend функцию download:

suspend fun download(url: String): File < networkService.download(url, object: NetworkService.Callback < override fun onSuccess(result: File) < >>) >

В колбэк onSuccess придет загруженный файл.

Как вы помните из прошлого урока, suspend функция должна результаты своей работы передать в Continuation.invokeSuspend. Для этого используется метод Continuation.resume

suspend fun download(url: String): File < networkService.download(url, object: NetworkService.Callback < override fun onSuccess(result: File) < continuation.resume(result) >>) >

Метод resume содержит определенную логику, часть которой — это вызов метода invokeSuspend с передачей туда результата.

Осталось где-то взять continuation. Для этого мы используем функцию suspendCoroutine.

suspend fun download(url: String): File < return suspendCoroutine < continuation ->networkService.download(url, object: NetworkService.Callback < override fun onSuccess(result: File) < continuation.resume(result) >>) > >

Весь наш код уходит в блок suspendCoroutine. Этот блок дает нам доступ к continuation, в который мы передаем результат работы networkService.download.

Получается, что suspend функция выполнила асинхронную работу и результат передала в continuation.resume, а тот уже передаст его в continuation.invokeSuspend.

Возврат ошибки

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

Пусть у NetworkService.Callback есть метод onFailure. Он будет вызван, если при загрузке произошла ошибка. В нем мы вызовем метод continuation.resumeWithException и передадим туда исключение.

suspend fun download(url: String): File < return suspendCoroutine < continuation ->networkService.download(url, object: NetworkService.Callback < override fun onSuccess(result: File) < continuation.resume(result) >override fun onFailure(error: Exception) < continuation.resumeWithException(error) >>) > >

В этом случае ошибка не пойдет в метод invokeSuspend, а будет обработана корутиной. Т.е. в этом случае continuation не продолжит свое выполнение и код, который в корутине находится после suspend функции не будет выполнен.

Простая suspend функция download готова. Ее можно вызывать в корутине, она не будет блокировать поток, но приостановит код.

Можно ли сразу вернуть результат

Я все время упоминаю, что suspend функция должна сначала выполнить свою работу, а потом вернуть результат. Но возможен и другой вариант в случае, если результат уже готов на момент вызова. В примере с функцией загрузки файла — это может быть использование кэша. suspend функция сначала проверяет кэш. Если файл уже есть в кэше, то функция сразу может вернуть его. Для этого ей надо просто сразу вызвать continuation.resume. А если файла в кэше нет, то действуем по старой схеме — стартуем асинхронную работу и по ее завершению вызываем continuation.resume.

Давайте посмотрим как в Kotlin добавить в suspend функцию такую возможность:

suspend fun download(url: String): File < return suspendCoroutine < continuation ->val file = getFileFromCache(url) if (file != null) < continuation.resume(file) >else < networkService.download(url, object : NetworkService.Callback < override fun onSuccess(result: File) < continuation.resume(result) >override fun onFailure(error: Exception) < continuation.resumeWithException(error) >>) > > >

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

Блокирование потока

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

Давайте рассмотрим пример некорректной suspend функции. Представим, что используемый в примере выше NetworkService теперь работает синхронно. Его метод download не уходит в фоновый поток и не просит колбэк, а загружает файл в текущем потоке и возвращает как результат вызова метода. Т.е. метод networkService.download блокирует поток, в котором он вызван. Если мы используем его напрямую в suspend функции, то получится неправильная suspend функция:

// wrong suspend function, don't do that! suspend fun download(url: String): File < return suspendCoroutine < continuation ->val file = networkService.download(url) continuation.resume(file) > >

Важно понимать, что ни метод suspendCoroutine, ни слово suspend в описании функции не cделают наш код асинхронным. Метод networkService.download будет выполнен в потоке корутины и заблокирует его. Поэтому от нас требуется обеспечить асинхронность кода в suspend функции, а в качестве колбэка использовать continuation.

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

Потерянная корутина

Что будет если в suspend функции не вызвать continuation.resume? Это очень важный момент! Если мы не вызовем ни один из методов continuation.resume*, то корутина просто не продолжит выполняться. Никогда. Потому что она ждет, что suspend функция продолжит ее через вызов Continuation. Поэтому тут будьте аккуратны, не теряйте вызов Continuation — передайте ему результат или ошибку, чтобы корутина могла продолжиться или завершиться.

Зачем нужно слово suspend?

Теперь мы знаем достаточно про suspend функции, чтобы поговорить о значении слова suspend. Само по себе это слово не добавляет к функции никакой магии. Оно не сделает так, чтобы синхронный код вдруг перестал блокировать поток. Оно вообще ничего не делает. Это просто маркер того, что данная функция умеет (и должна) работать с Continuation, чтобы приостановить выполнение корутины не блокируя поток.

Давайте взглянем на это с двух точек зрения: создание suspend функции и ее использование.

Создание

Чтобы suspend функция могла приостановить код не блокируя поток, ей нужен Continuation, выполнение которого она возобновит по завершению своей работы. Чтобы получить Continuation, используется функция suspendCoroutine. Возникает вопрос, почему любая другая функция без слова suspend не может этого сделать? Давайте допустим, что мы можем создать обычную (не suspend) функцию и вызвать в ней suspendCoroutine, который предоставит нам Continuation. Если мы вызовем такую функцию в корутине, то все отработает нормально, потому что у корутины есть Continuation. Его мы и получим. Но если мы вызовем ее вне корутины, то suspendCoroutine не сможет предоставить нам Continuation, потому что его просто нет.

Получается, что функция, в которой мы хотим получить доступ к Continuation, должна запускаться только в корутине. В обычном коде ее запускать нельзя. И вот именно для реализации этого ограничения и используется слово suspend. Мы не сможем запустить suspend функцию вне корутины, компилятор выдаст ошибку. Потому что компилятор знает, что suspend функции нужен будет Continuation, который есть только в корутине. Таким образом, когда мы создаем функцию и добавляем к ней слово suspend — мы можем быть уверены, что она будет запущена в корутине. А значит мы сможем добраться до Continuation.

Использование

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

А обычная функция, которая не является suspend, не сможет такого сделать. Она либо заблокирует поток корутины, либо попросит дать ей колбэк.

Что происходит внутри suspendCoroutine?

Об этом я подробно расскажу в пятом уроке.

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

Как вызвать suspend функцию библиотеки Kotlin из кода Java?

Ситуация Есть корпоративная библиотека на Kotlin. В библиотеке есть основной метод, который в процессе работы обращается к suspend fun. Метод принимает только один аргумент и возвращает Boolean. Библиотекой должны пользоваться разработчики на Java, соответственно, вызывать из Java-кода. Основные асинхронные вызовы — это сетевое взаимодействие с помощью Ktor. В данный момент пишу шаблон проекта для разработчиков-джавистов и пытаюсь сам использовать библиотеку из Java-кода. В чем проблема При вызове метода из кода Java он начинает требовать второй аргумент — континуацию и возвращать не Boolean, а Object. В виде кода выгляит примерно так: Kotlin-библиотека:

class MainClass() < // Конструктор класса принимает некоторые агрументы, но это можно опустить в данной ситуации // Какие-то переменные, вспомогательные методы private suspend fun getResults(): Array < // Result - просто data class, ничего особенного. // Некоторые асинхронные вызовы >suspend fun mainFun(arg: ClassForParser): Boolean < // Принимает только один аргумент - класс, в который будет парситься JSON-строка в процессе // Код метода // Вызов getResults() return result >> 

Вызов из java:

public class SomeClass() < public static void main(String[] args) throws IOException < // Некоторый код, который корректно работает: собирает и вычисляет данные, компанует их перед отправкой в основной класс MainClass mainClass = new MainClass(); Boolean result = mainClass.mainFun(ClassForParsing, Continuation) // Появилась континуация как второй аргумент. //Метод теперь возвращает Object, а не Boolean >> 

Что удалось на данный момент. Удалось запустить метод, но вместо Boolean возвращает объект kotlin.coroutines.intrinsics.CoroutineSingletons Собственно, вопрос Что нужно сделать, чтобы метод запустился так, как я его задумывал? Где в моих знаниях был пробел, из-за которого такое получилось? Решение, которое не подойдет. Переписать либу на Java. Я не джавист, либа почти готова, а дедлайн близко. На это нет времени. Проще будет джавистов научить пользоваться либой на Kotlin. Спасибо.

Корутины

В последнее время поддержка асинхронности и параллельных вычислений стала неотъемлимой чертой многих языков программирования. И 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 . Она создает корутину в виде блока кода — в данном случае это:

и запускает эту корутину параллельно с остальным кодом. То есть данная корутина выполняется независимо от прочего кода, определенного в функции 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 < launch< for(i in 0..5)< println(i) delay(400L) >> >

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

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *