Сопрограммы :: Практический Опыт

В этой статье я расскажу о том, как работают сопрограммы и как их создавать.

Рассмотрим приложение в последовательном и параллельном исполнении.

Давайте поговорим об обработке ошибок, отладке и способах тестирования сопрограмм.

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

Статья подготовлена по материалам моего доклада на МБЛТ ДЕВ 2018 , в конце поста есть ссылка на видеозапись.



Последовательный стиль



Сопрограммы :: практический опыт

Рис.

2.1 Какова была цель разработчиков сопрограмм? Они хотели, чтобы асинхронное программирование было максимально простым.

Нет ничего проще, чем построчное выполнение кода с использованием синтаксических конструкций языка: try-catch-finally, циклов, условных операторов и так далее.

Давайте рассмотрим две функции.

Каждый выполняется в своем потоке (рис.

2.1).

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

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

2.1. Давайте посмотрим, как этого можно достичь.

Функции ЛонгОпОнБ, ЛонгОпОнА - так называемые приостановить -функции, перед выполнением которых поток освобождается, а после завершения своей работы снова становится занятым.

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

Это делается путем создания сопрограммы с использованием так называемого Coroutine Builder. На картинке это запуск , но есть и другие, например, асинхронный , runБлокировка .

О них я расскажу позже.

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

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



Управление жизненным циклом

Coroutine Builder дает нам в качестве возвращаемого значения jobu — подкласс класса.

Работа (рис.

2.2).

С его помощью мы можем управлять жизненным циклом сопрограммы.

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



Сопрограммы :: практический опыт

Рис.

2.2

Изменение потока

Вы можете изменить поток выполнения сопрограммы, изменив элемент контекста сопрограммы, отвечающий за диспетчеризацию.

(рис.

2.3) Например, сопрограмма 1 будет выполнена в пользовательский интерфейс -thread, пока сопрограмма 2 находится в потоке, взятом из пула Диспетчеры.

ИО .



Сопрограммы :: практический опыт

Рис.

2.3 Библиотека сопрограмм также предоставляет функцию приостановки.

withContext(CoroutineContext) , с помощью которого вы можете переключаться между потоками в контексте сопрограммы.

Таким образом, переход между потоками может быть довольно простым:

Сопрограммы :: практический опыт

Рис.

2.4. Запускаем нашу сопрограмму в UI-потоке 1 → показываем индикатор загрузки → переключаемся на рабочий поток 2, освобождая основной → выполняем там длинную операцию, которую невозможно выполнить в UI-потоке → возвращаем результат обратно в UI-поток 3 → и поработайте там с ним, отрисовывая полученные данные и скрывая индикатор загрузки.

Пока выглядит вполне комфортно, идем дальше.



Функция приостановки

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

Сопрограммы :: практический опыт

Рис.

2,5 Для управления состоянием сопрограммы в библиотеке предусмотрены функции вида suspendXXXXCoroutine , которые предоставляют аргумент, реализующий интерфейс Продолжение , используя методы резюмеWithException И резюме с помощью которого мы можем возобновить выполнение сопрограммы в случае ошибки и успеха соответственно.

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



Функция приостановки.

Отменить вызов

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

suspendCancellableCoroutine (рис.

2.6).

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

Следовательно, именно здесь вызов метода необходимо отменить.



Сопрограммы :: практический опыт

Рис.

2.6

Отображение изменений в пользовательском интерфейсе

Теперь, когда функция приостановки сетевых запросов подготовлена, вы можете использовать ее вызов в UI-потоке сопрограммы как последовательный, при этом во время выполнения запроса поток будет свободен, а для запуска будет использоваться дооснащенный поток.

запрос.

Таким образом, мы реализуем асинхронное по отношению к UI-потоку поведение, но пишем его в последовательном стиле (рис.

2.6).

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



Сопрограммы :: практический опыт

Рис.

2,7 К сожалению, это не все, что нам нужно для разработки приложений.

Давайте посмотрим на обработку ошибок.



Обработка ошибок: try-catch-finally. Отмена сопрограммы: CancellationException

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

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

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

(рис.

2.8)

Сопрограммы :: практический опыт

Рис.

2,8 Стандартная языковая конструкция try catchfinally становится доступной для обработки исключений.

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

Сопрограммы :: практический опыт

Рис.

2,9 Если сопрограмма отменяется, что можно сделать, вызвав метод Job#cancel, выдается исключение.

Исключение отмены .

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

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

Например, предусмотрена обработка ошибок в пользовательском интерфейсе, когда есть возможность «отменить» запросы или логирование ошибок.

В первом случае пользователю будет показана ошибка, хотя ее на самом деле не существует, а во втором — будет регистрироваться бесполезное исключение и засорять отчеты.

Чтобы игнорировать ситуацию отмены сопрограммы, нужно немного изменить код:

Сопрограммы :: практический опыт

Рис.

2.10

Регистрация ошибок

Давайте рассмотрим ситуацию со stacktrace исключений.

Если бросить исключение непосредственно в блоке кода сопрограммы (рис.

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

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



Сопрограммы :: практический опыт

Рис.

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

Например (рис.

2.12), если возобновить работу сопрограммы из ранее реализованной функции suspend с тем же исключением, что и в предыдущем примере, то stacktrace не предоставит информацию о том, где именно искать ошибку.



Сопрограммы :: практический опыт

Рис.

2.12 Чтобы понять, какая сопрограмма возобновилась с исключением, вы можете использовать элемент контекста Имя Сопрограммы .

(рис.

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

То есть как минимум будет понятно, где искать ошибку.

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

Сопрограммы :: практический опыт

Рис.

2.13

Регистрация ошибок.

Обработчик исключений

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

(рис.

2.14) Обработчик должен реализовать интерфейс Обработчик исключения CoroutineException .

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

В метод будет выброшено необработанное исключение.

handleException , где вы можете делать с ним все, что вам нужно.

Например, полностью игнорировать это.

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

Сопрограммы :: практический опыт

Рис.

2.14 Давайте посмотрим, как может выглядеть регистрация нашего исключения:

  1. Нам нужно помнить о Исключение отмены , который мы хотим игнорировать.

  2. Добавьте свои собственные журналы.

  3. Помните о поведении по умолчанию, которое включает в себя логирование и закрытие приложения, иначе исключение просто «исчезнет» и будет непонятно, что произошло.

Теперь, если генерируется исключение, в logcat будет отправлена распечатка трассировки стека с дополнительной информацией:

Сопрограммы :: практический опыт

Рис.

2.15

Параллельное исполнение.

асинхронный

Рассмотрим параллельную работу функций приостановки.

Лучше всего он подходит для организации параллельного получения результатов от нескольких функций.

асинхронный .

Асинхронный, например запуск — Конструктор сопрограмм.

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

Метод await будет ждать завершения выполнения сопрограммы, если она еще не завершена, в противном случае он немедленно вернет результат работы.

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

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

Сопрограммы :: практический опыт

Рис.

2.16 Представим, что перед нами стоит задача параллельного получения данных от двух функций.

Затем вам нужно объединить и отобразить их.

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

Этот случай часто встречается на практике.

В этом случае вам необходимо обработать ошибку следующим образом:

  1. Мы включаем обработку ошибок в каждую из асинхронных сопрограмм.

  2. В случае ошибки мы отменяем все сопрограммы.

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

  3. Придумываем дополнительную реализацию, чтобы понять, все ли данные успешно загружены.

    Например, мы предположим, что если await вернул значение null, то при получении данных произошла ошибка.

С учетом всего этого реализация родительской сопрограммы несколько усложняется.

Реализация асинхронных сопрограмм также усложняется:

Сопрограммы :: практический опыт

Рис.

2.17 Этот подход не является единственно возможным.

Например, вы можете реализовать параллельное выполнение с обработкой ошибок, используя Обработчик исключений или СупервайзерРабота .



Вложенные сопрограммы

Давайте посмотрим на работу вложенных сопрограмм.

По умолчанию вложенная сопрограмма создается с использованием области действия внешней и наследует ее контекст. В результате вложенная сопрограмма становится дочерней, а внешняя сопрограмма — родительской.

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

Это также будет полезно при выходе из экрана, когда вам нужно отменить текущие запросы.

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

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

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

Сопрограммы :: практический опыт

Рис.

2.18 Вы можете создать дочернюю сопрограмму из глобальной вложенной сопрограммы, заменив элемент контекста ключом Работа к родительскому заданию или полностью использовать контекст родительской сопрограммы.

Но в этом случае стоит помнить, что перенимаются все элементы родительской сопрограммы: пул потоков, обработчик исключений и так далее:

Сопрограммы :: практический опыт

Рис.

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



Контрольные точки

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

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

Сопрограммы :: практический опыт

Рис.

2.20 Теперь мы получаем данныеА используя вложенную сопрограмму, оставляя точку останова в logData :

Сопрограммы :: практический опыт

Рис.

2.21 Попытка развернуть этот блок и попытаться найти нужные значения не удалась.

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



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

Модульное тестирование довольно просто реализовать.

Для этого вы можете использовать Coroutine Builder runБлокировка .

runБлокировка блокирует поток до тех пор, пока не завершатся все его вложенные сопрограммы, а это именно то, что нужно для тестирования.

Например, если вы знаете, что где-то внутри метода для его реализации используется сопрограмма, то для тестирования метода вам нужно просто обернуть его в runБлокировка .

runБлокировка может использоваться для проверки функции приостановки:

Сопрограммы :: практический опыт

Рис.

2.22

Примеры

Наконец, я хотел бы показать несколько примеров использования сопрограмм.

Представим, что нам нужно параллельно выполнить три запроса А, Б и С, показать их завершение и отразить момент завершения запросов А и Б.

Для этого можно просто обернуть сопрограммы запросов А и Б в одну общую и работать с ней как единое целое:

Сопрограммы :: практический опыт

Рис.

2.23 В следующем примере показано, как можно использовать обычный цикл for для выполнения периодических запросов с интервалом в 5 секунд:

Сопрограммы :: практический опыт

Рис.

2.24

выводы

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

Есть сложности в отладке, небольшой шаблонность в реализации очевидных вещей.

В целом сопрограммы довольно просты в использовании, особенно для реализации простых асинхронных задач.

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

Сопрограммы легко тестировать, и все это поставляется из коробки от той же компании, которая разрабатывает язык.



Видеозапись доклада

Букв оказалось много.

Для тех, кто предпочитает слушать - видео из моего доклада на МБЛТ ДЕВ 2018 :

Полезные материалы по теме:

Теги: #Android #Разработка Android #Разработка мобильных приложений #программирование #Kotlin #coroutines #elegion
Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.