Flutter async await

Обзоры > Flutter > Flutter async await

Коротко не очень об асинхронности в dart (с примерами в Flutter). Статья написана в 2024 году. Актуальная версия Flutter 3.16.9 и Dart 3.2.6. Код на github. Асинхронное исполнения кода в Dart(Flutter) здесь описано с прицелом на программистов, приходящих в Flutter из других языков. Чтоб было поменьше сюрпризов 🙂

Ну и, как обычно, для себя, чтобы иногда возвращаться, вспоминать и, вероятно, переосмысливать…

Event Loop, однопоточность и первый нюанс

Dart — язык однопоточный. Как же выполняется «параллельный» или асинхронный код в Dart (Flutter)? При запуске приложения выполняется код в методе main и запускается бесконечный цикл Event Loop. Этот цикл проверяет две очереди: microTaskQueue и eventQueue. Если в очереди микротаск появляется задача/задачи, то выполняются они все, потом проверяется очередь событий. И, при появлении события/событий, выполняется первое (потом снова проверяются микротаски). С учетом того, что микротаски в коде Flutter используются меньше 10 раз, мы можем рассматривать только текущий исполняемый код и очередь событий.

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

Если мы хотим, при выполнении какого-то метода, вынести немного кода в «параллель», мы используем, в первую очередь, Future. Но, возвращаясь на пару абзацев назад, мы должны понимать, что код не будет выполняться одновременно с текущим, а будет помещен в очередь и выполнится потом (асинхронно, а не параллельно). Это в общем случае.

Примеры:

Первый пример (который приводится во многих аналогичных статьях, по моему, ничего не демонстрирует 🙂 Точнее, показывает, но не совсем понятно. Здесь намекается на то, что Future помещает свой метод в очередь и тот выполняется после текущего синхронного кода.

Вот такой пример будет, наверное, нагляднее. Здесь мы видим, что «in example 1» не вписывается нигде в цикле, т.е. не выполняется параллельно, а однозначно дожидается выполнения цикла. И это приводит нас к первому нюансу, который нужно учитывать при программировании на Dart (Flutter).

Здесь мы ожидаем, что функция в Future выполнится через секунду, после того, как мы ее обозначили, но (смотри про Event Loop) с начала выполнится код, расположенный дальше, (каким бы тяжелым он не был) и только потом наш колбек из фьючи(не через 1 секунду, как мы задали, а через две, три или больше). (Если кому-то 10000 тиков цикла мало, поставьте побольше 🙂 )

к содержанию ↑

Async await и еще один нюанс

Откладывание кода «в будущее» на столько-то секунд само по себе не столь интересно. Чаще всего мы не знаем, когда этот код точно выполнится, но нам важно, чтобы после его выполнения у нас сработал какой-нибудь колбек. Банальный пример, «дернули» АПИ на беке и, когда нам придет ответ с бэка, мы должны этот ответ как-то обработать. В Dart для этого есть then и cathcError.

Но, когда нам нужно выстраивать цепочку из таких асинхронных функций и прописывать множество then, код становится не очень читаемым. Чтобы этого избежать, в Dart есть async await, по сути, синтаксический сахар для Future().then.

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

Выглядит, на первый взгляд, приятнее и читабельнее. Но, надо заметить, что «await» может кого-то ввести в заблуждение, что код здесь останавливается и ждет… ждет…

На самом деле (нюанс второй) «await» — это, условно, команда «поместить код ниже в очередь, когда придет ответ из асинхронной функции». Но, при условии, что там есть отложенный код (Future).

Какой результат вы ожидаете, если метод и колбек, как ниже? Проверьте себя.

Хм… несколько неожиданно? Или так и предполагали? Мы не остановились ДО await (что логично), мы запустили функцию (а она оказалась синхронной в этом случае) и после получения ответа (сразу же) поместили колбек в очередь.

Таким образом asyncFunc3, описанная выше, аналогичная вот такой:

Ну и вопрос для самопроверки 🙂 Поняли ли вы вышеизложенное. В каком порядке будет меняться extValue?

А ответ у нас будет такой:

Чтобы совсем проникнуться «духом» async/await, поэкспериментируйте с четвертым примером сами. Уберите, хотя бы, await перед Future 🙂

И, надеюсь, уловите разницу. Что-то типа:

await — это значит, выполнить код, а по итогу поместить код, который ниже, в очередь; Future(без await) — это поместить код в очередь, а по итогу поместить колбек, если есть, в очередь.
к содержанию ↑

Comleter

Чтобы помочь нам в использовании Future имеется completer, который возвращает фьючу, но это уже другая история… Потому что, с одной стороны, это просто, а с другой не совсем понятно, как и где применять…

к содержанию ↑

Isolate и compute

А для работы в разных потоках у нас имеются Isolate и, младший брат, compute. И это точно должна быть отдельная статья.

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

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