Suspend fun kotlin что это
Урок 3. Корутины. Suspend функции
В этом уроке подробно разберем как создать suspend функции. Также рассмотрим, можно ли блокировать поток, как корутина может потеряться и зачем нужно слово suspend.
В прошлом уроке мы подробно рассмотрели взаимодействие Continuation и suspend функции со стороны Continuation. Теперь посмотрим на это взаимодействие со стороны suspend функций.
Как создать suspend функцию
Сейчас мы будем рассматривать, как создать suspend функцию из асинхронного кода. Если же у вас есть какой-то синхронный метод и его надо сделать suspend, то там просто используется билдер withContext. Об этом мы поговорим, когда начнем тему билдеров.
Давайте создадим suspend функцию download, которую мы использовали ранее в примерах.
Предположим, у нас есть некий NetworkService, который асинхронно умеет загружать файлы. И мы хотим обернуть его в suspend функцию download:
В колбэк onSuccess придет загруженный файл.
Как вы помните из прошлого урока, suspend функция должна результаты своей работы передать в Continuation.invokeSuspend. Для этого используется метод Continuation.resume
Осталось где-то взять continuation. Для этого мы используем функцию suspendCoroutine.
Весь наш код уходит в блок suspendCoroutine. Этот блок дает нам доступ к continuation, в который мы передаем результат работы networkService.download.
Получается, что suspend функция выполнила асинхронную работу и результат передала в continuation.resume, а тот уже передаст его в continuation.invokeSuspend.
Возврат ошибки
Кроме успешного результата, continuation может принять данные об ошибке.
Пусть у NetworkService.Callback есть метод onFailure. Он будет вызван, если при загрузке произошла ошибка. В нем мы вызовем метод continuation.resumeWithException и передадим туда исключение.
В этом случае ошибка не пойдет в метод invokeSuspend, а будет обработана корутиной. Т.е. в этом случае continuation не продолжит свое выполнение и код, который в корутине находится после suspend функции не будет выполнен.
Простая suspend функция download готова. Ее можно вызывать в корутине, она не будет блокировать поток, но приостановит код.
Можно ли сразу вернуть результат
Давайте посмотрим как в Kotlin добавить в suspend функцию такую возможность:
Мы пытаемся получить файл из кэша. Если он там есть, то сразу передаем его в continuation, иначе запускаем асинхронную работу.
Блокирование потока
Когда мы создаем suspend функцию, мы должны позаботиться о том, чтобы она выполнялась асинхронно в другом потоке или вернула результат сразу. Если же мы напишем в suspend функции код, блокирующий поток, то слово suspend нам тут никак не поможет. Такая suspend функция просто заблокирует поток, в котором выполняется корутина.
Давайте рассмотрим пример некорректной suspend функции. Представим, что используемый в примере выше NetworkService теперь работает синхронно. Его метод download не уходит в фоновый поток и не просит колбэк, а загружает файл в текущем потоке и возвращает как результат вызова метода. Т.е. метод networkService.download блокирует поток, в котором он вызван. Если мы используем его напрямую в suspend функции, то получится неправильная suspend функция:
Важно понимать, что ни метод suspendCoroutine, ни слово suspend в описании функции не cделают наш код асинхронным. Метод networkService.download будет выполнен в потоке корутины и заблокирует его. Поэтому от нас требуется обеспечить асинхронность кода в suspend функции, а в качестве колбэка использовать continuation.
О том, как в корутине выполнять синхронный код так, чтобы не блокировать поток корутины, мы поговорим позже, когда будем рассматривать билдеры.
Потерянная корутина
Зачем нужно слово suspend?
Теперь мы знаем достаточно про suspend функции, чтобы поговорить о значении слова suspend. Само по себе это слово не добавляет к функции никакой магии. Оно не сделает так, чтобы синхронный код вдруг перестал блокировать поток. Оно вообще ничего не делает. Это просто маркер того, что данная функция умеет (и должна) работать с Continuation, чтобы приостановить выполнение корутины не блокируя поток.
Давайте взглянем на это с двух точек зрения: создание suspend функции и ее использование.
Создание
Чтобы suspend функция могла приостановить код не блокируя поток, ей нужен Continuation, выполнение которого она возобновит по завершению своей работы. Чтобы получить Continuation, используется функция suspendCoroutine. Возникает вопрос, почему любая другая функция без слова suspend не может этого сделать? Давайте допустим, что мы можем создать обычную (не suspend) функцию и вызвать в ней suspendCoroutine, который предоставит нам Continuation. Если мы вызовем такую функцию в корутине, то все отработает нормально, потому что у корутины есть Continuation. Его мы и получим. Но если мы вызовем ее вне корутины, то suspendCoroutine не сможет предоставить нам Continuation, потому что его просто нет.
Использование
Если мы в корутине собираемся использовать долго работающую функцию, которая помечена как suspend, то мы точно знаем, что эта функция приостановит код и не заблокирует при этом поток, в котором выполняется корутина. Если конечно, она написана корректно)
А обычная функция, которая не является suspend, не сможет такого сделать. Она либо заблокирует поток корутины, либо попросит дать ей колбэк.
Что происходит внутри suspendCoroutine?
Об этом я подробно расскажу в пятом уроке.
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Под капотом модификатора suspend
Aug 5, 2020 · 8 min read
Корутины Kotlin предоставили Android разработчикам модификатор suspend. Изучив его, вы поймете, почему функция suspend не возвращает ничего до тех пор, пока не будет завершена вся начатая работа, и как код может приостановить работу без блокировки потоков.
TL; DR; компилятор Kotlin создаст конечный автомат для каждой функции suspend, которая управляет выполнением корутины за нас!
Корутины 101
Корутины упрощают асинхронные операции на Android. Как поясняется в документации, мы можем использовать их для управления асинхронными задачами, которые в противном случае могут заблокировать основной поток и привести к зависанию приложения.
Корутины также полезны для замены API, основанных на обратном вызове, на императивно выглядящий код. Например, взгляните на этот асинхронный код, который использует обратные вызовы:
Эти обрат н ые вызовы могут быть преобразованы в последовательные вызовы функций с помощью корутин:
В коде корутин мы добавили модификатор функции suspend. Он говорит компилятору, что эта функция должна выполняться внутри корутины. Как разработчик, вы можете рассматривать suspend как обычную функцию, выполнение которой может быть приостановлено и возобновлено в какой-то момент.
В отличие от обратных вызовов, корутины обеспечивают простой способ переключения между потоками и обработкой исключений.
Но что же на самом деле делает компилятор под капотом, когда мы помечаем функцию как suspend?
Под капотом suspend
Если кратко, то компилятор Kotlin берет функции suspend и преобразует их в оптимизированную версию обратных вызовов с использованием конечного автомата, о котором мы поговорим позже.
Вы правильно поняли, компилятор напишет эти обратные вызовы за вас!
Интерфейс Continuation
Давайте взглянем на его определение:
Компилятор заменит модификатор suspend дополнительным параметром completion (типа Continuation ) в сигнатуре функции, которая будет использоваться для передачи результата функции suspend вызвавшей ее корутине:
Примечание: если вы пометите функцию, которая не вызывает другие функции suspend с этим модификатором, компилятор добавит дополнительный параметр Continuation, но ничего с ним не сделает, байт-код тела функции будет выглядеть как обычная функция.
Вы также можете увидеть интерфейс Continuation в других местах:
• Вы можете запустить корутину с помощью функции расширения startCoroutine для функции suspend. Он принимает объект Continuation в качестве параметра, который будет вызван, когда новая корутина завершится либо результатом, либо исключением.
Применение диспетчеров
Вы можете переключаться между различными диспетчерами для выполнения вычислений в разных потоках. Откуда Kotlin знает, где возобновить приостановленные вычисления?
Сгенерированный конечный автомат
Внимание: код, который будет показан в остальной части статьи, не будет полностью соответствовать байт-коду, сгенерированному компилятором. Это будет код Kotlin, достаточно точный, чтобы позволить вам понять, что на самом деле происходит внутри. Это представление генерируется корутинами версии 1.3.3 и может измениться в будущих версиях библиотеки.
Компилятор Kotlin определит, когда функция может приостановить работу внутри системы. Каждая точка приостановки будет представлена как состояние в конечном автомате. Они представляются компилятором с помощью меток:
Для лучшего представления конечного автомата компилятор будет использовать оператор when для реализации различных состояний:
Этот код является неполным, поскольку различные состояния не имеют возможности обмениваться информацией. Для этого компилятор будет использовать тот же объект Continuation в функции. Вот почему исходный Continuation является Any? вместо возвращаемого типа исходной функции (т. е. User ).
Кроме того, компилятор создаст закрытый класс, который 1) содержит необходимые данные и 2) рекурсивно вызывает функцию loginUser для возобновления выполнения. Вы можете проверить приблизительную реализацию сгенерированного класса ниже.
Внимание: комментарии не генерируются компилятором. Я добавил их, чтобы объяснить, что выполняется в коде.
Первое, что ему нужно сделать, это знать, если 1) это первый раз, когда функция вызывается или 2) функция возобновилась из предыдущего состояния. Так он проверяет, имеет ли переданное continuation тип LoginUserStateMachine или нет:
Если это происходит в первый раз, он создаст новый экземпляр LoginUserStateMachine и сохранит полученный экземпляр completion в качестве параметра, чтобы он помнил, как возобновить функцию, вызвавшую его. Если это не так, то он просто продолжит выполнение конечного автомата (функция suspend).
Теперь давайте рассмотрим код, который компилятор генерирует для перемещения между состояниями и обмена информацией между ними:
Сравните код выше с предыдущими фрагментами. Давайте посмотрим, что генерирует компилятор:
Компилятор Kotlin делает для нас очень много. Из этой функции suspend:
Основы сопрограмм
В этом разделе рассматриваются основные концепции сопрограмм.
Ваша первая сопрограмма
Запустите следующий код:
Результат выполнения кода будет следующим:
Это связано с тем, что delay является функцией приостановки, которая не блокирует поток, а приостанавливает сопрограмму. Использовать её можно только из сопрограммы.
Связываем блокирующий и неблокирующий миры
Таким же образом можно писать модульные тесты для функций приостановки:
Структурированный параллелизм
Есть более лучшее решение. В нашем коде можно использовать структурированный параллелизм. Вместо запуска сопрограмм в GlobalScope, как мы обычно делаем с потоками (потоки всегда глобальные), мы можем запускать сопрограммы в области видимости выполняемой нами операции.
Scope builder
В дополнение к CoroutineScope, предоставляемой разными билдерами, можно объявить свою собственную область видимости с помощью билдера coroutineScope. Он создает область видимости и не завершается, пока не завершатся все запущенные дочерние сопрограммы.
Следующий пример это демонстрирует:
Обратите внимание, что сразу после сообщения «Task from coroutine scope» (во время ожидания выполнения вложенного launch) выполняется и выдаётся «Task from runBlocking», хотя выполнение coroutineScope еще не завершилось.
Извлечение функции
Легковесные сопрограммы
Запустите следующий код:
Данный код запускает 100 тысяч сопрограмм, каждая из которых через 5 секунд печатает точку.
А теперь попробуйте сделать то же самое с потоками. Что произойдёт? (Скорее всего это вызовет ошибку, связанную с нехваткой памяти).
Глобальные сопрограммы похожи на демон-потоки
Нижеприведённый код запускает длительную сопрограмму в GlobalScope, которая два раза в секунду выводит сообщение «I’m sleeping», а затем, после некоторой задержки, происходит возврат из функции main :
Если вы запустите данный код, то увидите, что он трижды выводит сообщение и завершается:
Активные сопрограммы, запущенные в GlobalScope, не поддерживают «жизнь» процесса. В этом они похожи на демон-потоки.
Корутины в Kotlin (гайд)
Simon Wirtz в своем блоге публикует достаточно много интересных постов о Kotlin.
Представляю вашему вниманию перевод одного из них.
Введение и мотивация
Как я уже упоминал в Twitter несколько дней назад, я планировал подробнее посмотреть корутины из Kotlin, что я и сделал. Но, к сожалению, это заняло больше времени, чем я ожидал. По большей части это связано с тем, что корутины — это очень объемная тема, в особенности, если вы не знакомы с их концепцией. Так или иначе, хочу поделиться своим взглядом с вами и надеюсь преподнести вам исчерпывающий обзор.
Корутины — это, несомненно, одна из “больших фич”, как сказано в блоге JetBrains:
Другими словами, корутины были представлены для простой реализации многопоточного программирования. Наверняка многие из вас работали с Java, ее Thread-классом и классами для многопоточного программирования. Я и сам много с ними работал и убежден в зрелости их решений.
Многопоточность Java vs Kotlin корутины
Если вы все еще испытываете сложности с потоками и многопоточностью в Java, то я вам рекомендую книгу Java Concurrency in Practice.
Конечно, реализация из Java, с инженерной точки зрения, хорошо спроектирована, но ее сложно использовать в повседневной работе и она достаточно многословна. Помимо этого, в Java не так много реализаций для не блокирующего программирования. Часто можно себя поймать на том, что, запуская поток, ты совсем забываешь, что быстро попадаешь в блокирующий код (на блокировках, ожиданиях и т.д.) Альтернативный не блокирующий подход тяжело применять в повседневной работе, и в нем легко ошибиться.
Корутины, с другой стороны, выглядят как простой последовательный код, пряча всю сложность внутри библиотек. В то же время они предоставляют возможность запускать асинхронный код без всяких блокировок, что открывает большие возможности для различных приложений. Вместо блокировки потоков вычисления становятся прерываемыми. JetBrains описывают корутины как “легковесные потоки”, конечно, не те Threads, что мы знаем в Java. корутины очень дешевы в создании, и накладные расходы в сравнении с потоками не идут ни в какое сравнение. Как вы дальше увидите, корутины запускаются в Threads под управлением библиотеки. Другое весомое отличие — ограничения. Количество потоков ограничено, так как они на самом деле соответствуют нативным потокам. Создание корутины, с другой стороны, практически бесплатно, и даже тысячи их могут быть легко запущены.
Стили многопоточного программирования
В различных языках можно встретить самые разные подходы к многопоточному программированию: основанные на callback (JavaScript), future/promise (Java, JavaScript), async/await подход (С#) и т.д. Все они могут быть реализованы при помощи корутин благодаря тому, что они не навязывают стиль программирования. Напротив, любой стиль либо уже реализован, либо может быть реализован с их помощью.
Концепция корутин
Нельзя сказать, что понятие “корутины” новое. Согласно статье из Wikipedia, само название уже было известно в 1958 году. Многие современные языки программирования предоставляют нативную поддержку: C#, Go, Python, Ruby, и т.д. Реализация корутин, и в Kotlin в том числе, часто основана на так называемых “Continuations”, которые являются абстрактным представлением управляемого состояния в компьютерных программах. Мы еще вернемся к тому, как они работают (реализация корутин).
Начало. Основы
Есть исчерпывающий материал, доступный на сайте kotlinlang.org, в котором хорошо описано, как настроить проект для работы с корутинами. Посмотрите подробней на материал по предыдущей ссылке или просто возьмите за основу код из моего репозитория на GitHub.
Ингредиенты корутин
Как уже было сказано, библиотека с корутинами предоставляет понятный высокоуровневый API, который позволяет нам быстро начать работу. Единственный модификатор, который нужно выучить, это suspend. Он используется в качестве дополнительного модификатора у методов, чтобы пометить их как прерываемые.
Прерываемые функции
Как видно из примера выше, функция с прерыванием выглядит как обычная функция с дополнительным модификатором. Имейте в виду, что такие функции могут быть вызваны только из корутин, иначе это приведет к ошибкам компиляции.
Корутины могут быть как последовательностью обычных функций, так и функций с прерыванием с необязательным результатом, который будет доступен после выполнения.
Перейдем к примерам
После всех бла-бла-бла перейдем к конкретным примерам. Начнем с основ:
Заглянем глубже в кроличью нору
Рассмотрим пример, более приближенный к реальности. Представьте, что в приложении вам нужно отправить email. Запрос получателей и генерация текста сообщения занимают значительное время. Оба процесса независимы, и соответственно мы можем их выполнять параллельно.
Можно немного упростить код автора
async билдер
Мы уже видели “ожидающую” часть Deferred объектов из Kotlin в (7), где прерываемая функция вызывалась с результатами ожидания обоих методов. Метод await() вызывается на экземпляре объекта Deferred, вызов которого прерывается до того, пока не будет доступен результат. Вызов sendEmail также обернут в асинхронный билдер, чтобы мы могли подождать выполнения.
Что пропустили: CoroutineContext
Общее изменяемое состояние
Вы, наверное, уже задумались: корутины выглядят, конечно, хорошо, но как же мы будем выполнять синхронизацию и как мы будем обмениваться данными между различными корутинами?
Что ж, это как раз тот вопрос, которым я недавно задавался, и это резонный вопрос для большинства корутин, использующих пул потоков. Для синхронизации мы можем использовать различные техники: потокобезопасные структуры данных, ограничение на выполнение в одном потоке или использовать блокировки (посмотрите подробнее Mutex )
Помимо общих паттернов, корутины из Kotlin поощряют нас использовать стиль “обмен через коммуникацию” (смотрите QA).
В частности, для коммуникации хорошо подходит “актор”. Его можно использовать в корутинах, которые могут отправлять/принимать сообщения от него. Давайте посмотрим на примере:
Акторы
Каналы (Channel)
Channels предоставляют нам возможность обмена потоком значений, что очень похоже на то, как мы используем BlockingQueue (реализуя паттерн producer-consumer) в Java, только без всяких блокировок. Кроме того, send и receive являются прерываемыми функциями и используются для получения и отправки сообщений через канал, реализующий FIFO стратегию.
Как мы видим, все состояние изолировано в конкретном акторе. Это решает проблему общего изменяемого состояния.
Другие функциональности и примеры
Если вы хотите глубже погрузиться в корутины и поработать с ними, то я рекомендую прочитать подробнее документацию Kotlin и в особенности посмотреть отличный гайд.
Как они работают — реализация корутин
Я не стану слишком глубоко погружаться в детали, чтобы не перегружать пост. Кроме того, в следующие несколько недель я планирую написать продолжение с более детальной информацией о реализации, вместе с примерами генерации байткода. Поэтому сейчас ограничимся простым описанием “на пальцах”.
Советы от Романа Елизарова
Не так давно мне удалось поговорить с Романом Елизаровым из JetBrains, благодаря которому во многом и появились корутины в Kotlin. Позвольте мне поделиться полученной информацией с вами:
В: Первый вопрос, который у меня возникает: когда нужно использовать корутины и есть ли такие ситуации, где все еще необходимо будет использовать потоки?
О: Корутины нужны для асинхронных задач, которые ожидают чего-либо большую часть времени. Потоки для интенсивных CPU задач.
В: Я упоминал, что фраза “легковесные потоки” звучит немного обманчиво для меня, в особенности, если учитывать то, что корутины основаны на потоках и выполняются в пуле потоков. Мне кажется, корутины больше похожи на “таск”, который выполняется, прерывается, останавливается.
О: Фраза “легковесные потоки” скорее поверхностна, корутины во многом ведут себя как потоки с точки зрения пользователей
В: Мне бы хотелось узнать о синхронизации. Если корутины во многом похожи на потоки, то тогда будет необходимо реализовывать синхронизацию общего состояния между различными корутинами.
О: Можно использовать известные паттерны для синхронизации, но все же предпочтительней вообще не иметь общего состояния при использовании корутин. Вместо этого корутины “поощряют стиль обмена через коммуникацию”.
Выводы
Корутины — очень мощный функционал, который появился в Kotlin. Пока я не познакомился с корутинами, мне казалось, что многопоточности из Java вполне достаточно.
Корутины также позволяют нам использовать различные подходы для написания конкурентного кода, каждый из которых либо уже реализован в библиотеке (kotlinx.coroutine), либо может быть легко воплощен с ее помощью.
Пересмотрите ваш подход для конкурентного программирования в Java, все эти проверяемые исключения, жестко блокирующие стратегии и огромное количество шаблонного кода. С корутинами вполне нормально писать код последовательно при помощи вызовов suspend функций, общаясь с другими корутинами, ожидая результата, отменяя корутины и т.д.
Перспективы
Все же я убежден, что корутины действительно невероятны. Конечно, время покажет — являются ли они действительно зрелыми для высоконагруженных многопоточных приложений. Может, даже многие программисты подумают и пересмотрят свои подходы к программированию. Любопытно посмотреть, что будет дальше. Ну а сейчас, корутины пока находятся в экспериментальной стадии, значит, JetBrains могут еще адаптировать их в предстоящих релизах, основываясь на отзывах сообщества, представители которого уже вовсю пробуют их в бою и пытаются адаптировать для своих задач.
Отлично! Вы дочитали до конца весь пост. Надеюсь, что вы нашли что-нибудь полезное для себя. Буду рад любому отзыву.
Сопрограммы
Сопрограммы получили статус стабильные в Kotlin 1.3. Детали см. ниже
Некоторые API инициируют долго протекающие операции (такие как сетевой ввод-вывод, файловый ввод-вывод, интенсивная обработка на CPU или GPU и др.), которые требуют блокировки вызывающего кода в ожидании завершения операций. Сопрограммы обеспечивают возможность избежать блокировки исполняющегося потока путём использования более дешёвой и управляемой операции: приостановки (suspend) сопрограммы.
Сопрограммы упрощают асинхронное программирование, оставив все осложнения внутри библиотек. Логика программы может быть выражена последовательно в сопрограммах, а базовая библиотека будет её реализовывать асинхронно для нас. Библиотека может обернуть соответствующие части кода пользователя в обратные вызовы (callbacks), подписывающиеся на соответствующие события, и диспетчировать исполнение на различные потоки (или даже на разные машины!). Код при этом останется столь же простой, как если бы исполнялся строго последовательно.
Многие асинхронные механизмы, доступные в других языках программирования, могут быть реализованы в качестве библиотек с помощью сопрограмм Kotlin. Это включает в себя async / await из C# и ECMAScript, channels и select из языка Go, и generators / yield из C# или Python. См. описания ниже о библиотеках, реализующих такие конструкции.
Блокирование против приостановки
Главным отличительным признаком сопрограмм является то, что они являются вычислениями, которые могут быть приостановлены без блокирования потока (вытеснения средствами операционной системы). Блокирование потоков часто является весьма дорогостоящим, особенно при интенсивных нагрузках: только относительно небольшое число потоков из общего числа является активно выполняющимися, поэтому блокировка одного из них ведет к затягиванию какой-нибудь важной части итоговой работы.
С другой стороны, приостановка сопрограммы обходится практически бесплатно. Не требуется переключения контекста (потоков) или иного вовлечения механизмов операционной системы. И сверх этого, приостановка может гибко контролироваться пользовательской библиотекой во многих аспектах: в качестве авторов библиотеки мы можем решать, что происходит при приостановке, и оптимизировать, журналировать или перехватывать в соответствии со своими потребностями.
Еще одно отличие заключается в том, что сопрограммы не могут быть приостановлены на произвольной инструкции, а только в так называемых точках остановки (приостановки), которые вызываются в специально маркируемых функциях.
Останавливаемые функции
Приостановка происходит в случае вызова функции, обозначенной специальным модификатором suspend :
Такие функции называются функциями остановки (приостановки), поскольку их вызовы могут приостановить выполнение сопрограммы (библиотека может принять решение продолжать работу без приостановки, если результат вызова уже доступен). Функции остановки могут иметь параметры и возвращать значения точно так же, как и все обычные функции, но они могут быть вызваны только из сопрограмм или других функций остановки. В конечном итоге, при старте сопрограммы она должна содержать как минимум одну функцию остановки, и функция эта обычно анонимная (лямбда-функция остановки). Давайте взглянем, для примера, на упрощённую функцию async() (из библиотеки kotlinx.coroutines ):
Продолжая аналогию, await() может быть функцией остановки (также может вызываться из блока async <> ), которая приостанавливает сопрограмму до тех пор, пока некоторые вычисления не будут выполнены, и затем возвращает их результат:
Отметим, что функции приостановки await() и doSomething() не могут быть вызваны из обыкновенных функций, подобных main() :
Заметим, что функции остановки могут быть виртуальными, и при их переопределении модификатор suspend также должен быть указан:
Aннотация @RestrictsSuspension
Внутреннее функционирование сопрограмм
Мы не стремимся здесь дать полное объяснение того, как сопрограммы работают под капотом, но примерный смысл того, что происходит, очень важен.
Сопрограммы полностью реализованы с помощью технологии компиляции (поддержка от языковой виртуальной машины, среды исполнения, или операционной системы не требуется), а приостановка работает через преобразование кода. В принципе, каждая функция приостановки (оптимизации могут применяться, но мы не будем вдаваться в эти подробности здесь) преобразуется в конечный автомат, где состояния соответствуют приостановленным вызовам. Прямо перед приостановкой следующее состояние загружается в поле сгенерированного компилятором класса вместе с сопутствующими локальным переменными и т. д. При возобновлении сопрограммы локальные переменные и состояние восстанавливаются, и конечный автомат продолжает свою работу.
Приостановленную сопрограмму можно сохранять и передавать как объект, который хранит её приостановленное состояние и локальные переменные. Типом таких объектов является Continuation, а преобразование кода, описанное здесь, соответствует классическому Continuation-passing style. Следовательно, приостановливаемые функции принимают дополнительный параметр типа Continuation (сохранённое состояние) под капотом.
Более детально о том, как работают сопрограммы, можно узнать в этом проектном документе. Похожие описания async / await в других языках (таких как C# или ECMAScript 2016) актуальны и здесь, хотя особенности их языковых реализаций могут существенно отличаться от сопрограмм Kotlin.
Экспериментальный статус сопрограмм сменился на стабильный
Важное замечание: мы рекомендовали авторам библиотек, начавшим использовать эксперементальные сопрограммы следовать той же конвенции: добавить к названию суффикс «экспериментальный» (например, com.example.experimental ), указывающий, какой там используется сопрограммно совместимый API. Таким образом ваша библиотека сохранит бинарную совместимость. Сейчас, когда вышел финальный API-интерфейс, выполните следующие действия:
Это позволит минимизировать проблемы миграции для пользователей.
Поддержка экспериментальной версии сопрограмм будет прекращена в Kotlin 1.4
Стандартные API
Сопрограммы представлены в трёх их главных ингредиентах:
Низкий уровень API: kotlin.coroutines
Низкоуровневый API относительно мал и должен использоваться ТОЛЬКО для создания библиотек высокого уровня. Он содержит два главных пакета:
Более детальная информация о использовании этих API может быть найдена здесь.
API генераторов в kotlin.coroutines
Это функции исключительно «уровня приложения» в kotlin.coroutines :
Чтобы продемонстрировать реальную ленивость такой последовательности, давайте напечатаем некоторые отладочные результаты изнутри вызова sequence():
Чтобы сразу породить всю коллекцию (или последовательность) значений, доступна функция yieldAll() :
Функция iterator() во всём подобна sequence(), но только возвращает ленивый итератор.
Другие API высокого уровня: kotlinx.coroutines
Только базовые API, связанные с сопрограммами, доступны непосредственно из стандартной библиотеки Kotlin. Они преимущественно состоят из основных примитивов и интерфейсов, которые, вероятно, будут использоваться во всех библиотеках на основе сопрограмм.
Эти библиотеки являются удобными API, которые делают основные задачи простыми. Также они содержат законченные примеры того, как создавать библиотеки, построенные на сопрограммах.