Написание блокирующего и неблокирующего кода с помощью сопрограмм
Одна из интересных особенностей Kotlin - сопрограммы. В разработке Android управление потоками всегда было немного сложным, потому что существуют ограничения на работу с вещами, связанными с пользовательским интерфейсом или основным потоком. И было обычной практикой использовать пользовательский интерфейс или основной поток для операций, связанных с пользовательским интерфейсом, и фоновые потоки для блокировки таких операций, как сетевые вызовы, операции с базой данных, тяжелые вычислительные задачи и т. Д.
Если управление потоками не выполняется должным образом, это оказывает огромное влияние на производительность приложения, приводя к ошибкам ANR, зависанию пользовательского интерфейса, сбоям и т. Д. Поэтому очень важно делать что-то в правильном потоке.
Управление потоками или многопоточность может быть достигнуто разными способами. Но Kotlin Coroutines предоставляют самое простое решение для обработки потоков. Сопрограммы - это не что иное, как легкие потоки. Они предоставляют нам простой способ синхронного и асинхронного программирования. Сопрограммы позволяют нам заменять обратные вызовы и создавать основную безопасность, не блокируя основной поток.
Посмотрим, как это сделать.
Примечание. Класс AsyncTask
устарел в Android 11. Сопрограммы Kotlin теперь являются рекомендуемым решением для асинхронного кода.
Использование функций приостановки
Давайте разберемся в этом на простом примере получения сведений о пользователе из запроса API или локальной базы данных и отображения их пользователю. Если мы попытаемся получить детали через вызов API в основном потоке, он выдаст NetworkOnMainThreadException
. Если мы извлечем данные из базы данных, эта операция заблокирует выполнение основным потоком своих операций до тех пор, пока данные не будут извлечены, что приведет к плохому взаимодействию с пользователем.
Мы можем просто сделать это, используя suspend
функции в сопрограммах. suspend
означает, что метод, связанный с этим модификатором, является синхронным и не будет возвращен немедленно. Связанный с ним метод будет приостановлен на некоторое время, а затем вернет результат, когда он станет доступным.
Давайте визуализируем это более наглядно:
Когда сопрограмма приостановлена, она не выполняется. Это приостановлено. И когда он возобновляется, он возобновляет работу с того места, на котором остановился. Когда мы вызываем функцию suspend
в основном потоке, эта функция приостанавливается и выполняет свою работу в любом другом рабочем потоке. Как только это будет сделано, он возобновится с результатом, чтобы мы могли использовать результат в основном потоке.
Но есть некоторые ограничения на использование suspend
функций. Их можно вызывать либо внутри построителя сопрограмм, либо из другой suspend
функции.
Диспетчеры используются для указания, в каком потоке должна выполняться сопрограмма. Они похожи на планировщики в Rx. Мы можем указать, для каких диспетчеров мы хотим, чтобы запрос fetchUser
API выполнялся (в нашем случае это Dispatchers.IO
). Мы используем метод withContext
, чтобы указать это:
После выполнения запроса API мы можем использовать результат в основном потоке, используя Dispatchers.Main
. Доступны следующие диспетчеры:
Dispatchers.Default
: Это диспетчер сопрограмм по умолчанию, который используется всеми разработчиками сопрограмм, такими какlaunch
,async
и т. Д., Если в их контексте не указан диспетчер или какой-либо другойContinuationInterceptor
. Он используется в таких сценариях, как выполнение интенсивной работы ЦП вне основного потока.Dispatchers.IO
: Этот диспетчер сопрограмм предназначен для блокирования операций ввода-вывода диска и сетевых операций. Дополнительные потоки в этом пуле создаются и закрываются по запросу.Dispatchers.Main
: диспетчер сопрограмм, который ограничен основным потоком , работающим с объектами пользовательского интерфейса. Доступ к этому свойству может вызватьIllegalStateException
, если в пути к классам нет диспетчеров основного потока. Он используется в основном для получения результатов ответа от операций API, операций с базой данных и т. Д.
Используйте соответствующий диспетчер, исходя из ваших требований.
Что происходит под капотом
Компилятор Kotlin записывает обратные вызовы, когда вычисление может приостановиться. Сопрограммы вызывают эти обратные вызовы как продолжение . Продолжение - это не что иное, как общий интерфейс обратного вызова с некоторой дополнительной информацией в нем. Конечный автомат будет примерно таким:
Структурированный параллелизм
Структурированный параллелизм - это система шаблонов проектирования в сопрограммах, которая пытается устранить утечки памяти. И это помогает нам задуматься над следующими моментами:
- Кто может отменить выполнение сопрограммы?
- Есть ли у сопрограммы жизненный цикл?
- У кого возникает исключение, когда сопрограмма дает сбой?
Структурированный параллелизм управляет этими вещами, вводя концепцию, называемую областью видимости.
Область сопрограмм
CoroutineScope
- это интерфейс в пакете kotlinx.coroutines.
- Он отслеживает сопрограммы.
- Он дает возможность отменить сопрограмму.
- Он будет уведомлен всякий раз, когда произойдет сбой.
Область сопрограммы - это не что иное, как переменная, которую очень легко создать. Он не будет содержать ссылок на какие-либо тяжелые предметы. Поэтому всякий раз, когда мы хотим управлять жизненным циклом сопрограммы, нам нужно создать область видимости. Его легко создать следующим образом:
Теперь сопрограммы, созданные выше с помощью launch
построителя сопрограмм, будут следовать жизненному циклу области. Если возникнут какие-либо исключения, об этом будет сообщено в область действия, чтобы мы могли их обработать. Во многих случаях, когда пользователи закрывают экран во время выполнения сетевого запроса, нам необходимо отменить все текущие запросы. Таким образом, в обратном вызове ViewModel
, onCleared()
или в обратном вызове действия onDestroy()
мы можем отменить область действия, чтобы все сопрограммы прекратили свое выполнение.
Отмена прицела означает:
- Он отменяет все запущенные дочерние сопрограммы.
- Мы не можем запускать больше сопрограмм с этой областью видимости.
Обработка исключений области сопрограммы
Область также принимает задание в качестве параметра.
Задание можно использовать для определения жизненного цикла области видимости и сопрограммы. Если мы передадим задание в область видимости, оно будет обрабатывать исключения определенным образом. Когда с областью связано несколько дочерних элементов:
- Если какой-либо из дочерних элементов потерпит неудачу, он распространит исключение на других дочерних элементов.
- Когда сообщается об ошибке, область действия отменяет себя и распространяет исключение вверх.
Остановка других дочерних элементов и выдача исключения не идеальны в большинстве случаев сбоя, часто приводящего к сбою. В этих случаях нам нужно использовать SupervisorJob
.
Когда мы используем SupervisorJob
, он не останавливает выполнение других дочерних элементов в той же области. И когда сообщается о сбое, осциллограф ничего не делает. Поскольку исключение может распространяться вверх, нам, возможно, придется использовать try/catch
блоков в нашем коде для безопасности.
Я напишу отдельную статью об обработке исключений сопрограмм, области видимости и т. Д.
Как создать сопрограмму
Мы можем создавать сопрограммы, используя доступные конструкторы сопрограмм. В основном мы используем два:
launch
async
запуск
launch{}
- это конструктор сопрограмм, который создает новую сопрограмму "запустил и забыл". Функция launch
создает сопрограмму и немедленно возвращается. Однако работа продолжается в фоновом пуле потоков. Он запускает и забывает сопрограмму. Обычно используется, когда возвращаемого значения не ожидается. Например, если мы хотим загрузить что-то на сервер и нас не волнует результат:
асинхронный
async{}
- это построитель сопрограмм, который создает новую сопрограмму и используется в случае ожидаемого возвращаемого значения. Например, давайте рассмотрим приведенный выше пример получения пользовательских данных из вызова API:
async
вернет отложенный объект. Мы вызываем await()
для отложенного объекта, поэтому await
будет ждать, приостановить выполнение сопрограммы до тех пор, пока async
не завершит вычисление, и вернет значение сопрограммы.
Сравнение
launch
и async
не suspend
функции. Они являются отправной точкой для сопрограмм. Мы можем рассматривать их как функции расширения поверх области видимости.
Выводы
- Не отмечайте функцию
suspend
, если она не нужна. - Проверьте методы
yield
илиensureActive
в тяжелых вычислительных операциях, таких как чтение файлов, чтобы всякий раз, когда сопрограмма отменяется и вызывается эта функция, она останавливает выполнение этой сопрограммы. - Обработка исключений. В противном случае это может привести к сбою приложения.
Резюме
Теперь вы должны понять, что такое сопрограммы и их основное использование. Мы узнаем больше об этих сопрограммах, областях видимости и обработке исключений в моих следующих статьях.
Кроме того, я многому научился из ссылок, упомянутых ниже. Не забудьте их проверить.