Асинхронность, Future и Stream
Dart выполняет весь ваш код в одном потоке, чередуя куски работы. Future —
это одно значение, которое придёт позже; Stream — много таких значений.
Понимание того, как под всем этим устроен event loop, отличает «поставить await
и надеяться» от знания, что и когда выполняется.
Почему это важно. Почти любой плавающий баг во Flutter-приложении — это баг порядка: что-то завершилось после того, как экран уже уничтожен; два запроса вошли в гонку; подписка пережила свой widget. Ни один из них не воспроизводится стабильно, и все они растут из асинхронности, которую никто не продумал.
Что нужно понимать
- Что выполняется, пока вы стоите на
await, — и что не выполняется - Нужен ли ещё результат операции к моменту, когда она завершится
- Кто владеет подпиской и кто её закрывает
- Допустимо ли, чтобы две операции, способные наложиться друг на друга, действительно наложились
- Куда уходит ошибка, если её никто не ждёт
Ключевые темы
Event loop
- Микрозадачи и события — и почему порядок их выполнения удивляет
- Что на самом деле делает
await: приостановиться здесь, продолжить позже, в том же потоке - Блокировка цикла — и почему тяжёлая синхронная работа подвешивает UI
Future
- Создание, цепочки и разница между «вернуть» и «дождаться»
Future.waitдля параллельной работы и её принцип «всё или ничего» при ошибке- Ошибки: try/catch вокруг
await,catchErrorи необработанные ошибки - Таймауты и отмена, которых Dart не даёт из коробки
Stream
- Single-subscription против broadcast — и цена неверного выбора
- Преобразования:
map,where,asyncMap,distinct,debounceчерез сторонние пакеты - Backpressure — быстрый производитель и медленный потребитель
StreamControllerи его закрытие- Подписка как ресурс со своим временем жизни
Типичные сбои
- Работа завершается, когда widget уже уничтожен
- Два одновременных запроса приходят не в том порядке
- Утёкшие подписки
asyncтам, где результат никто не ждёт, — и ошибки исчезают бесследно
Уровни
| Уровень | Как это выглядит |
|---|---|
| Junior | Корректно применяет async/await для простых вызовов и обрабатывает ошибки вокруг них. |
| Middle | Осознанно выбирает между Future и Stream, отменяет подписки, справляется с гонками и таймаутами. |
| Senior | Рассуждает о порядке выполнения в масштабе целой фичи. Проектирует конвейеры потоков с backpressure и корректной очисткой, а плавающий баг умеет объяснить с самых основ. |
Практика
Для начала
-
Обработайте ошибку по-настоящему Возьмите
awaitбез обработки ошибок и задайте разное поведение для сбоя и для таймаута. -
Запустите два вызова параллельно Замените два последовательных
awaitнаFuture.wait, а затем решите, что должно произойти, если один из вызовов упадёт. -
Закройте подписку Найдите
listen, которому не соответствует ни одинcancel, и приведите жизненный цикл в порядок.
Дальше
-
Выиграйте гонку Сделайте поле поиска по мере ввода так, чтобы медленный ранний ответ не мог перезаписать быстрый поздний.
-
Соберите конвейер Составьте поток с debounce, устранением дублей и восстановлением после ошибок — и покройте его тестами.
-
Найдите микрозадачу Напишите код, порядок вывода которого вас удивит, а затем объясните его через event loop.
Проверьте себя
- Что произойдёт в вашем приложении, если ответ придёт после того, как пользователь ушёл с экрана?
- Какие из ваших потоков — broadcast, и был ли это осознанный выбор?
- Где два запроса могут войти в гонку и кто выигрывает её сегодня?
- Что случится с исключением, брошенным внутри
async-функции, которую никто не ждёт? - Как вы отменяете работу, результат которой больше не нужен?
- Что в вашем приложении способно заблокировать event loop настолько, чтобы пропустить кадр?
Материалы
- Asynchronous programming: futures, async, await —
официальный codelab и правильное место, чтобы починить шаткое понимание
await, прежде чем браться за что-то ещё. - Concurrency in Dart — event loop, изоляты и то, что на самом деле значит «однопоточный». Коротко и принципиально важно.
- Using streams — потребление и преобразование потоков, включая обработку ошибок, которую большинство руководств пропускает.
- Stream class reference — стоит один раз прочитать целиком. Большая часть того, что люди пишут руками, здесь уже есть готовым методом.
- Dart asynchronous programming: isolates and event loops — от команды Dart, самый внятный разбор того, как цикл работает на самом деле.