Если ваше Android-приложение когда-либо зависало во время загрузки данных или ваш асинхронный код превращался в лабиринт коллбэков, вам необходимо освоить корутины Kotlin. По сути, эти инструменты позволяют нам писать процессы, которые не блокируют основной поток, гарантируя, что пользовательский интерфейс остается гибким в то время как мы ведем напряженную работу за кулисами.
Секрет идеальной работы заключается не просто в случайном создании сопрограмм, а в точном знании их принципов. где они исполняютсяЗдесь вступают в игру диспетчеры, действуя подобно дирижерам оркестра и определяя, какой поток будет обрабатывать каждую задачу, чтобы избежать перегрузки процессора или зависания экрана.
Столпы исполнения: Диспетчеры
En КотлинМы не можем просто бросить сопрограмму в пустоту; ей всегда нужен диспетчер. Диспетчер.Главный Этот компонент отвечает за всё, что видит пользователь. Он используется исключительно для взаимодействия с пользовательским интерфейсом, обновления LiveData или запуска быстрых функций. Если вы попытаетесь выполнить здесь сложные вычисления, приложение неизбежно начнет тормозить.
Для задач, требующих чтения файлов, записи в базу данных Room или выполнения HTTP-запросов, у нас есть следующее: Dispatchers.IOЭтот диспетчер оптимизирован для задач ввода/вывода, где поток тратит много времени на ожидание внешнего ответа. Поэтому он может масштабироваться до 64 потоков, поскольку Оно не потребляет ресурсы процессора интенсивно. пока жду.
Когда проблема заключается в вычислительной мощности, например, при обработке огромного JSON-файла или сортировке гигантского списка, Dispatchers.Default Это правильный вариант. В отличие от варианта с вводом-выводом, этот подходит. количество физических ядер вашего процессора. Нет смысла создавать сотню потоков, если у вас всего четыре ядра, потому что система будет тратить драгоценное время на переключение контекстов.
Также есть Диспетчеры. Без ограничений.Хотя в реальных проектах это встречается крайне редко, выполнение начинается в том потоке, где вызывается функция, а затем возобновляется с того места, где завершилась приостановленная функция. не рекомендуется если только вы точно не знаете, что делаете.
Управление выполнением с помощью withContext и безопасности основного потока.
Одна из лучших практик в Android — это разработка всех функций таким образом, чтобы они были реализованы наилучшим образом. безопасно для основного потокаЭто достигается с помощью withContext()Вместо того чтобы заставлять вызывающую сторону знать, в каком потоке следует запускать функцию, сама функция берет на себя изменение контекста.
Когда мы используем withContext(Dispatchers.IO)Сопрограмма приостанавливается на текущем потоке, перемещается в пул ввода-вывода для выполнения основной работы, и после её завершения... возвращает результат В исходную ветку обсуждения. Самое приятное то, что это не добавляет никаких дополнительных накладных расходов на производительность по сравнению со старыми коллбэками и позволяет писать код, который выглядит линейным, но на самом деле является асинхронным.
Важно понимать, что обозначение функции как suspend Это не означает, что она автоматически выполняется во вторичном потоке. Приостановленная функция может прекрасно выполняться в основном потоке; её функция делает следующее: приостановить выполнение не блокируя поток, сохраняя состояние в стековом кадре для последующего возобновления его работы.
Управление жизненным циклом: области применения и задачи
Запуск сопрограмм с использованием GlobalScope в большинстве случаев является огромной ошибкой, поскольку это очень сложно протестировать и может привести к утечкам памяти. В идеале следует использовать... области применения, связанные с жизненным цикломНа Android у нас есть viewModelScope для ViewModels и lifecycleScope для мероприятий или фрагментов.
Когда Activity уничтожается, его lifecycleScope оно автоматически отменяется, что, в свою очередь, останавливает все вредоносные процедуры Активная в этой области. Это предотвращает попытки приложения обновить интерфейс, которого больше не существует, предотвращая типичные сбои из-за отсутствующих ссылок.
Каждый раз, когда мы используем launch o async, мы получаем Объект заданияЭта задача подобна пульту дистанционного управления, позволяющему нам контролировать сопрограмму. Мы можем использовать job.cancel() прервать выполнение задачи или job.join() если нам нужно дождаться завершения одной сопрограммы, прежде чем перейти к следующей.
Параллелизм и реальный параллелизм
Многие путают параллельное выполнение с многопоточностью. Параллельное выполнение похоже на жонглера, управляющего несколькими шарами: кажется, будто они летят одновременно, но он касается только одного за раз. реальный параллелизм Это происходит, когда несколько ядер процессора одновременно работают над разными задачами.
Для этого мы используем конструктор. async, что возвращает нам DeferredЕсли мы запустим две задачи с async(Dispatchers.Default) На многоядерном мобильном устройстве будут работать обе программы. в точно такое же времяДля получения значений мы просто вызываем соответствующую функцию. await().
Если мы хотим запустить несколько задач и убедиться, что все они завершатся, прежде чем переходить к следующей, самый чистый способ — использовать coroutineScope { ... }Это создает среду структурированная параллельность где, если одна из дочерних задач завершается с ошибкой, область видимости обрабатывает ошибку и предотвращает незаметное проникновение исключений, что могло бы произойти, если бы мы использовали async без него await обеспокоены.
Анатомия переключения контекста
Изменение контекста или переключение контекста Это процесс, в ходе которого операционная система приостанавливает один поток, сохраняет его состояние и загружает другой. Хотя сопрограммы являются легковесными, переключение между ними... Dispatchers.Default y Dispatchers.IO Это включает в себя перемещение выполнения между различными пулами потоков.
Если мы будем чрезмерно использовать переключения контекста, производительность может пострадать. Однако фреймворк Kotlin очень умный и оптимизирует прыжкиЕсли вы уже находитесь в совместимом пуле потоков, система попытается сохранить вас в том же потоке, чтобы избежать ненужных накладных расходов, связанных с сохранением и загрузкой регистров ЦП.
Важное замечание: использование пулов потоков не гарантирует, что сопрограмма всегда будет выполняться в одном и том же потоке от начала до конца. После suspend y un
resumeВозможно, что сопрограмма проснуться в другой ветке обсужденияПоэтому полагаться на локальные переменные потока (ThreadLocal) может быть рискованно.
Для эффективного управления асинхронными рабочими процессами идеальный подход заключается в запуске сопрограммы в основном потоке с использованием соответствующей области видимости, делегируя основную работу другим задачам. Dispatchers.IO o Default использование withContextи использовать силу async когда нам нужны результаты параллельно, чтобы приложение работало быстро, а пользовательский опыт был безупречным.
