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

Suspend fun 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() ), корутина возобновляет свою работу в одном из свободных потоков.

Сопрограммы

Некоторые API инициируют долго протекающие операции (такие как сетевой ввод-вывод, файловый ввод-вывод, интенсивная обработка на CPU или GPU и др.), которые требуют блокировки вызывающего кода в ожидании завершения операций. Сопрограммы обеспечивают возможность избежать блокировки исполняющегося потока путём использования более дешёвой и управляемой операции: приостановки (suspend) сопрограммы.

Сопрограммы упрощают асинхронное программирование, оставив все осложнения внутри библиотек. Логика программы может быть выражена последовательно в сопрограммах, а базовая библиотека будет её реализовывать асинхронно для нас. Библиотека может обернуть соответствующие части кода пользователя в обратные вызовы (callbacks), подписывающиеся на соответствующие события, и диспетчировать исполнение на различные потоки (или даже на разные машины!). Код при этом останется столь же простой, как если бы исполнялся строго последовательно.

Многие асинхронные механизмы, доступные в других языках программирования, могут быть реализованы в качестве библиотек с помощью сопрограмм Kotlin. Это включает в себя async / await из C# и ECMAScript, channels и select из языка Go, и generators / yield из C# или Python. См. описания ниже о библиотеках, реализующих такие конструкции.

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

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

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

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

Останавливаемые функции

Приостановка происходит в случае вызова функции, обозначенной специальным модификатором suspend :

suspend fun doSomething(foo: Foo): Bar

Такие функции называются функциями остановки (приостановки), поскольку их вызовы могут приостановить выполнение сопрограммы (библиотека может принять решение продолжать работу без приостановки, если результат вызова уже доступен). Функции остановки могут иметь параметры и возвращать значения точно так же, как и все обычные функции, но они могут быть вызваны только из сопрограмм или других функций остановки. В конечном итоге при старте сопрограммы она должна содержать как минимум одну функцию остановки, и функция эта обычно анонимная (лямбда-функция остановки). Давайте взглянем, для примера, на упрощённую функцию async() (из библиотеки kotlinx.coroutines ):

fun async(block: suspend () -> T) 

T`. So, when we pass a lambda to `async()`, it is a *suspending lambda*, and we can call a suspending function from it: —>

Здесь async() является обычной функцией (не функцией остановки), но параметр block имеет функциональный тип с модификатором suspend : suspend () -> T . Таким образом, когда мы передаём лямбда-функцию в async() , она является анонимной функцией остановки, и мы можем вызывать функцию остановки изнутри её:

async

Продолжая аналогию, await() может быть функцией остановки (также может вызываться из блока async <> ), которая приостанавливает сопрограмму до тех пор, пока некоторые вычисления не будут выполнены, и затем возвращает их результат:

async

Больше информации о том, как действительно работают функции async/await в kotlinx.coroutines , может быть найдено здесь.

Отметим, что функции приостановки await() и doSomething() не могут быть вызваны из обыкновенных функций, подобных main() :

fun main(args: Array) < doSomething() // ERROR: Suspending function called from a non-coroutine context >

Заметим, что функции остановки могут быть виртуальными, и при их переопределении модификатор suspend также должен быть указан:

interface Base < suspend fun foo() >class Derived: Base < override suspend fun foo() < . >> 

Aннотация @RestrictsSuspension

Расширяющие функции (и анонимные функции) также могут быть маркированы как suspend , подобно и всем остальным (регулярным) функциям. Это позволяет создавать DSL и другие API, которые пользователь может расширять. В некоторых случаях автору библиотеки необходимо запретить пользователю добавлять новые пути приостановки сопрограммы.

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

Это актуально в тех редких случаях, когда каждая приостановка обрабатывается специальным образом в библиотеке. Например, при реализации генераторов через buildSequence() функцию, описанную ниже, мы должны быть уверены, что любой приостанавливаемый вызовов в сопрограмме завершается вызовом либо yield() , либо yieldAll() , а не какой-либо другой функции. Именно по этой причине SequenceBuilder аннотирована с @RestrictsSuspension:

@RestrictsSuspension public abstract class SequenceBuilder

Внутреннее функционирование сопрограмм

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

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

Приостановленную сопрограмму можно сохранять и передавать как объект, который хранит её приостановленное состояние и локальные переменные. Типом таких объектов является Continuation, а преобразование кода, описанное здесь, соответствует классическому Continuation-passing style. Следовательно, приостановливаемые функции принимают дополнительный параметр типа Continuation (сохранённое состояние) под капотом.

Более детально о том, как работают сопрограммы, можно узнать в этом проектном документе. Похожие описания async / await в других языках (таких как C# или ECMAScript 2016) актуальны и здесь, хотя особенности их языковых реализаций могут существенно отличаться от сопрограмм Kotlin.

Экспериментальный статус сопрограмм сменился на стабильный

Из-за былого экспериментального статуса сопрограмм все связанные API были собраны в стандартной библиотеке как пакет kotlin.coroutines.experimental . Дизайн стабилизирован и его экспериментальный статус снят, окончательный API перенесен в пакет kotlin.coroutines , а экспериментальный пакет хранится в целях обеспечения обратной совместимости.

Важное замечание: мы рекомендовали авторам библиотек, начавшим использовать экспериментальные сопрограммы следовать той же конвенции: добавить к названию суффикс «экспериментальный» (например, com.example.experimental ), указывающий, какой там используется сопрограммно совместимый API. Таким образом ваша библиотека сохранит бинарную совместимость. Сейчас, когда вышел финальный API-интерфейс, выполните следующие действия:

  • скопируйте все API в com.example (без experimental суффикса);
  • сохраните экспериментальный вариант пакета для обратной совместимости.

Это позволит минимизировать проблемы миграции для пользователей.

Поддержка экспериментальной версии сопрограмм будет прекращена в Kotlin 1.4

Стандартные API

Сопрограммы представлены в трёх их главных ингредиентах:

  • языковая поддержка (функции остановки, как описывалось выше),
  • низкоуровневый базовый API в стандартной библиотеке Kotlin,
  • API высокого уровня, которые могут быть использованы непосредственно в пользовательском коде.

Низкий уровень API: kotlin.coroutines

Низкоуровневый API относительно мал и должен использоваться ТОЛЬКО для создания библиотек высокого уровня. Он содержит два главных пакета:

  • kotlin.coroutines — главные типы и примитивы, такие как:
    • createCoroutine()
    • startCoroutine()
    • suspendCoroutine()

    Более детальная информация о использовании этих API может быть найдена здесь.

    API генераторов в kotlin.coroutines

    Это функции исключительно «уровня приложения» в kotlin.coroutines :

    Они перенесены в рамки kotlin-stdlib , поскольку они относятся к последовательностям. По сути, эти функции (и мы можем ограничиться здесь рассмотрением только sequence() ) реализуют генераторы, т. е. предоставляют лёгкую возможность построить ленивые последовательности:

    import kotlin.coroutines.* fun main(args: Array) < //sampleStart val fibonacciSeq = sequence < var a = 0 var b = 1 yield(1) while (true) < yield(a + b) val tmp = a + b a = b b = tmp >> //sampleEnd // Print the first eight Fibonacci numbers println(fibonacciSeq.take(8).toList()) > 

    Это сгенерирует ленивую, потенциально бесконечную последовательность Фибоначчи, используя сопрограмму, которая дает последовательные числа Фибоначчи, вызывая функцию yield (). При итерировании такой последовательности на каждом шаге итератор выполняет следующую часть сопрограммы, которая генерирует следующее число. Таким образом, мы можем взять любой конечный список чисел из этой последовательности, например fibonacciSeq.take(8).toList() , дающий в результате [1, 1, 2, 3, 5, 8, 13, 21] . И сопрограммы достаточно дёшевы, чтобы сделать это практичным.

    Чтобы продемонстрировать реальную ленивость такой последовательности, давайте напечатаем некоторые отладочные результаты изнутри вызова sequence():

    import kotlin.coroutines.* fun main(args: Array) < //sampleStart val lazySeq = sequence < print("START ") for (i in 1..5) < yield(i) print("STEP ") >print("END") > // Print the first three elements of the sequence lazySeq.take(3).forEach < print("$it ") >//sampleEnd > 

    Запустите приведенный выше код, чтобы убедиться, что если мы будем печатать первые три элемента, цифры чередуются со STEP -ами по ветвям цикла. Это означает, что вычисления действительно ленивые. Для печати 1 мы выполняем только до первого yield(i) и печатаем START по ходу дела. Затем, для печати 2 , нам необходимо переходить к следующему yield(i) , и здесь печатать STEP . То же самое и для 3 . И следующий STEP никогда не будет напечатан (точно так же как и END ), поскольку мы никогда не запрашиваем дополнительных элементов последовательности.

    Чтобы сразу породить всю коллекцию (или последовательность) значений, доступна функция yieldAll() :

    import kotlin.coroutines.* fun main(args: Array) < //sampleStart val lazySeq = sequence < yield(0) yieldAll(1..10) >lazySeq.forEach < print("$it ") >//sampleEnd > 

    Функция iterator() во всём подобна sequence(), но только возвращает ленивый итератор.

    Вы могли бы добавить собственную логику выполнения функции sequence() , написав приостанавливаемое расширение класса SequenceScope (что порождается аннотацией @RestrictsSuspension , как описывалось выше):

    import kotlin.coroutines.* //sampleStart suspend fun SequenceScope.yieldIfOdd(x: Int) < if (x % 2 != 0) yield(x) >val lazySeq = sequence < for (i in 1..10) yieldIfOdd(i) >//sampleEnd fun main(args: Array) < lazySeq.forEach < print("$it ") >> 

    Другие API высокого уровня: kotlinx.coroutines

    Только базовые API, связанные с сопрограммами, доступны непосредственно из стандартной библиотеки Kotlin. Они преимущественно состоят из основных примитивов и интерфейсов, которые, вероятно, будут использоваться во всех библиотеках на основе сопрограмм.

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

    • Платформенно-зависимое асинхронное программирование с помощью kotlinx-coroutines-core :
      • этот модуль включает Go-подобные каналы, которые поддерживают select и другие удачные примитивы
      • исчерпывающее руководство по этой библиотеке доступно здесь.

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

      © 2015—2024 Open Source Community

      Котлин корутины. Часть 4. Переход callback API на корутины

      В третьей части этой серии вы узнали, как тестировать корутины через поведение. В этой части мы конвертируем существующий API, который работает на основе методов обратного вызова (колбеков). Заменим колбеки на корутины .

      Чтобы на практике увидеть работу с Kotlin Coroutines, Room и архитектурными компонентами, записывайтесь на продвинутый курс по разработке приложения «Чат-мессенжер»

      1. Откройте проект в Android Studio
      2. Исследуйте существующий callback API
      3. Преобразование callback API в suspend функцию
      4. Как насчет отмены?
      5. suspendCoroutine для преобразования callback API в корутины
      6. Что такое функция расширения?

      Откройте проект в Android Studio

      Для начала откройте проект kotlin-coroutines-repository в Android Studio. Исходный код можно найти в первом уроке.

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

      1. MainDatabase реализует базу данных с использованием Room, которая сохраняет и загружает Title .
      2. MainNetwork реализует сетевой API, который выбирает новый заголовок. Он использует поддельную сетевую библиотеку, определенную в FakeNetworkLibrary.kt для того, чтобы получать названия. Сетевая библиотека будет случайным образом возвращать ошибки.
      3. TitleRepository реализует единый API для извлечения или обновления заголовка путем объединения данных из сети и базы данных.
      4. MainViewModelTest определяет тест для MainViewModel .
      5. FakeNetworkCallAwaitTest это тест, который мы закончим позже в этом уроке.

      Исследуйте существующий callback API

      Откройте MainNetwork.kt и посмотрите на декларацию fetchNewWelcome()

      // MainNetwork.kt fun fetchNewWelcome(): FakeNetworkCall

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

      Эта функция возвращает FakeNetworkCall, что позволяет регистрировать слушателя для этого сетевого запроса. Вызов fetchNewWelcome запустит длительный сетевой запрос в другом потоке и вернет объект, который устанавливает addOnResultListener . Ваш код передает обратный вызов addOnResultListener который будет вызван, когда запрос завершится успехом или ошибкой.

      // TitleRepository.kt fun refreshTitle(/* . */) < val call = network.fetchNewWelcome() call.addOnResultListener < result ->// callback called when network request completes or errors when (result) < is FakeNetworkSuccess-> < // process successful result >is FakeNetworkError -> < // process network error >> > >

      Преобразование callback API в suspend функцию

      Функция refreshTitle в настоящее время реализована с использованием колбеков на FakeNetworkCall. Цель этого упражнения – представить наш сетевой API-интерфейс как suspend функцию, чтобы refreshTitle можно было переписать с использованием корутин.

      Для этого Kotlin предоставляет функцию suspendCoroutine, которая используется для преобразования API на основе колбеков в suspend функции.

      Вызов suspendCoroutine немедленно приостановит текущую корутину suspendCoroutine и предоставит вам объект continuation, который вы можете использовать для возобновления корутины. Continuation содержит весь контекст, необходимый для продолжения или возобновления приостановленной корутины.

      Continuation , которое предоставляет suspendCoroutine, имеет две функции: resume и resumeWithException. Вызов любой функции приведет к немедленному возобновлению suspendCoroutine.

      Вы можете использовать suspendCoroutine для приостановки перед ожиданием обратного вызова. Затем после колбека вызывается resume вызов или resumeWithException для возобновления с результатом колбека.

      Пример suspendCoroutine выглядит следующим образом:

      // Example of suspendCoroutine /** * A class that passes strings to callbacks */ class Call < fun addCallback(callback: (String) ->Unit) > /** * Exposes callback based API as a suspend function so it can be used in coroutines. */ suspend fun convertToSuspend(call: Call): String < // 1: suspendCoroutine and will immediately *suspend* // the coroutine. It can be only *resumed* by the // continuation object passed to the block. return suspendCoroutine < continuation ->// 2: pass a block to suspendCoroutine to register callbacks // 3: add a callback to wait for the result call.addCallback < value ->// 4: use continuation.resume to *resume* the coroutine // with the value. The value passed to resume will be // the result of suspendCoroutine. continuation.resume(value) > > >

      В этом примере показано, как использовать suspendCoroutine для преобразования API на основе обратного вызова при вызове в функцию приостановки. Теперь вы можете использовать Call непосредственно в коде на основе сопрограмм, например:

      // Example of using convertToSuspend to use a callback API in coroutines suspend fun exampleUsage() < val call = makeLongRunningCall() convertToSuspend(call) // suspends until the long running call completes >

      Вы можете использовать этот шаблон для предоставления функции приостановки на FakeNetworkCall, которая позволяет вам использовать сетевой API на основе колбеков в корутинах.

      Как насчет отмены?

      suspendCoroutine – хороший выбор, когда вам не нужно поддерживать cancellation. Как правило, однако, cancellation является проблемой, и вы можете использовать suspendCancellableCoroutine для распространения cancellation в библиотеки, которые поддерживают cancellation в API на основе колбеков.

      suspendCoroutine для преобразования callback API в корутины

      Прокрутите до конца TitleRepository.kt и найдите TODO для реализации функции расширения.

      /** * Suspend function to use callback-based [FakeNetworkCall] in coroutines * * @return network result after completion * @throws Throwable original exception from library if network request fails */ // TODO: Implement FakeNetworkCall.await() here

      Замените этот TODO этой функцией расширения на FakeNetworkCall :

      suspend fun FakeNetworkCall.await(): T < return suspendCoroutine < continuation ->addOnResultListener < result ->when (result) < is FakeNetworkSuccess-> continuation.resume(result.data) is FakeNetworkError -> continuation.resumeWithException(result.error) > > > >

      Эта suspend функция использует suspendCoroutine для преобразования API на основе колбеков в suspend функцию. Корутины могут вызвать await и будут немедленно приостановлены, пока результат сети не будет готов. Результатом сети является возвращаемое значение await, и ошибки будут вызывать исключение.

      Вы можете использовать это так:

      // Example usage of await suspend fun exampleAwaitUsage() < try < val call = network.fetchNewWelcome() // suspend until fetchNewWelcome returns a result or throws an error val result = call.await() // resume will cause await to return the network result >catch (error: FakeNetworkException) < // resumeWithException will cause await to throw the error >>

      Стоит потратить секунду, чтобы прочитать сигнатуру suspend функции. Ключевое слово suspend сообщает Kotlin, что это доступно корутинам. В результате он может вызывать другие suspend функции, такие как suspendCoroutine. Остальная часть объявления, fun FakeNetworkCall .await (), определяет функцию расширения, которая вызывает await для любого FakeNetworkCall. На самом деле он не изменяет класс, но при вызове из Kotlin он отображается как public метод. Тип возвращаемого значения await – T, который указывается после имени функции.

      Что такое функция расширения?

      Если вы новичок в Kotlin, функции расширения могут быть новой концепцией. Функции расширения не модифицируют класс, вместо этого они вводят новую функцию, которая принимает this в качестве первого аргумента.

      fun await(this: FakeNetworkCall): T

      Внутри тела await функции, this связан с переданным FakeNetworkCall . Вот как await вызывает addOnResultListener . Он использует неявный this так же, как метод члена.

      Все вместе эта подпись означает, что мы добавляем функцию приостановки, называемую await (), в класс, который изначально не был создан для корутин. Этот подход можно использовать для обновления API на основе колбеков для поддержки корутин без изменения реализации.

      В следующем упражнении мы напишем тесты для await () и узнаем, как вызывать корутины непосредственно из тестов.

      Вам також може сподобатися

      Урок 14. Навигация по условию в андроид приложении. Android Conditional Navigation & Firebase Authentication

      Создаем android-приложения 0 7 898

      Продолжаем серию уроков по разработке android-приложений в Android Studio на языке Kotlin. Пришло время

      hilt

      Инструменты android разработчика 0 14 429
      В поисках надежной, но простой структуры внедрения зависимостей (DI), не так давно я пришел

      Урок 11. Принципы навигации внутри и между андроид-приложениями

      Архитектура андроид-приложений 0 10 396

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

      Kotlin Coroutines

      Архитектура андроид-приложений 0 5 970
      Во второй части этой серии вы узнали, как управлять пользовательским интерфейсом при помощи корутин.

      Kotlin Coroutines

      Архитектура андроид-приложений 0 15 799

      Управление пользовательским интерфейсом через корутины В этом упражнении мы напишем корутину для отображения сообщения

      Как вызвать 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. Спасибо.

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

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