Всем привет, читатели Habr! В этой статье я хочу простыми словами рассказать про асинхронность и параллельность в Dart/Flutter. Многие новички сталкиваются с непониманием того, что это и когда что использовать. Я сам столкнулся с этой проблемой в начале своего карьерного пути и хочу простыми словами рассказать, что это такое и когда нужно.
В начале я хочу представиться) Меня зовут Владимир Калашников, я заканчиваю последний курс кафедры искусственного интеллекта и уже больше года профессионально занимаюсь разработкой приложений на Flutter. Несмотря на свой небольшой опыт, я уже успел поработать на проекте от компании, у которой оборот денежных средства более 60 мрлд$ за 2017 год. Я думаю, мне есть что рассказать :)
Асинхронность и Future
И так, начнем с асинхронности. В Dart она представлена двумя типами: Future и Streams. И в начале я расскажу о Future.
Представим себе такую ситуацию, Вы включили чайник, чтобы заварить чай. Вода не закипает мгновенно, по этому приходится ждать. Вы уже понимаете, что чай сможете себе сделать через 5 минут, то есть в будущем. В это время Вы возможно хотите полистать ленту в Instagram или посмотреть ролик на YouTube. У Вас нет желания смотреть 5 минут на чайник и ждать, пока вода закипит. Если перенести эту ситуацию в мобильную разработку, то нужно позволить пользователю выполнять другие операции, пока, например, идет загрузка данных.
Допустим приложение делает запрос в сеть, чтобы отобразить список продуктов. В этот момент пользователь возможно хочет переключаться между вкладками приложения. Если сделаете весь ваш код синхронным, то у него не будет такой возможности. И я гарантирую, через 5 минут в крайнем случае, приложение будет удалено. Это еще не наихудший случай, может потом прилететь негативный отзыв.
Теперь дальше развиваем тему с Dart на примерах из реальной жизни. Future может пребывать в двух состояниях: completed и uncompleted. В свою очередь completed так может пребывать в двух состояниях. Когда все пошло по маслу и Вы получили, что хотели, однако так бывает не всегда и Вы ловите ошибку. По аналогии с чайником: он закипел и Вы сделали себе чай, пошли дальше смотреть свой сериальчик) Либо же живете в районе, где часто вырубают свет, и вот в очередной раз его отрубили и чайник выключился (сочувствую, если реально живете в таком районе).
Теперь давайте поговори немного о практике. Приходит к Вам заказчик и говорит, что завтра демо перед инвесторами и нужно показать какой-то UI. А разработчик бека в этом момент уехал в отпуск) Вы берете и мокаете данные. И пока данные грузятся, нужно показать индикатор загрузки. Как это сделать? Конечно же использовать Future.
![](https://habrastorage.org/getpro/habr/upload_files/663/55e/0fb/66355e0fb176d2278942830a0b33318c.png)
Про асинхронность вроде бы все рассказал, теперь можно поговорить и про Streams. Они тоже имеют хорошую аналогию из жизни. Может Вы уже слышали про паттерн Observer, а может и нет.
Теперь про Streams
Наша жизнь постоянно меняется. Вокруг нас происходят кучу событий. Назовем это поток событий. И иногда нам нужно реагировать на эти события мгновенно. Допустим мы собираемся переходить дорогу и видим как между нами едут машины. Назовем эту ситуацию поток машин. И когда мы видим, что остается 3 секунды до красного цвета, а машина едет со скоростью 100 км/час, то дорогу переходить не хочется. И если мы уже начали это делать, а из-за угла выскочил водитель, то нам нужно мгновенно среагировать и отбежать в сторону.
Как Вы поняли, переходя дорогу, мы подписываемся на поток машин. А когда перешли, на душе уже спокойно и мы от этого потока отписываемся) Однако вероятнее Вы переходили дорогу с другими людьми. И они тоже были подписаны на поток машин.
Бывает такое, что у вас есть желание подписаться только одному, а если кто-то другой попытается подписаться, то ему дулю с маслом) Команда Dart конечно же об этом подумала, по этому есть 2 типа Stream: single subscription и broadcast. Помимо этого, у них отличается еще немного механизм работы, подробности найдете в документации.
Конечно же нужно привести пример как я использую стримы в своих проектах. Представим такую ситуацию, что у вас есть слайдер, а снизу него есть индикатор текущего слайда. Если на проекте подгорает, то можно сделать быстро и перерисовать всю страницу через setState(), когда меняется виджет в слайдере. Если времени больше, то лучше сделать так: создать класс контроллер, где будет Stream<int> и в него будет пушится текущий индекс слайдера. Тем временем, обернуть виджет индикатора в StreamBuilder и подписать ваш индикатор на этот стрим. Теперь у Вас не будет перерисовываться вся страница. И ваши юзеры со старыми телефонами скажут спасибо.
Много написал, давайте добавим кода. Недавно нужно было сделать такую задачу - по нажатию кнопки “показать больше” расширить текст, после этого по нажатию на “скрыть” уменьшить его до 4-х строк. 15 минут времени и готово.
![](https://habrastorage.org/getpro/habr/upload_files/cd5/59c/c3c/cd559cc3cbcf2fe629a4fd12e16bb26e.png)
![](https://habrastorage.org/getpro/habr/upload_files/3f5/050/002/3f505000242960ce42d185f36174c939.png)
И перед завершением моего рассказа о Stream хочу напомнить, что не забывайте отписываться от потоков. Иначе утечка памяти и веселые баги постучаться к вам в двери)
Вот практически мы подошли к концу и пора рассказать про параллельность в языке Dart.
В конце про Isolates
Иногда бывают задачи, которые мы не способны выполнить. Если Вы ходите в спортзал, то допустим поднять 200кг. И вдруг вы захотели снять ролик с поднятием такого веса. Что делать в такой ситуации? Делегировать эту задачу другому естественно.
Для этого в языке Dart есть Isolates. С ними я впервые столкнулся когда реализовал шифрование/дешифрование файлов в своем первом Flutter проекте. Я помню свое лицо, когда смотрел на тормозящий телефон и асинхронные методы и думал, что это за черная магия. После чтения документации compute() решил все проблемы.
Также я помню один вечер после работы, когда мой ментор нарисовал картинку на доске, а именно разницу между асинхронностью и параллельностью. Настало время поделиться ей.
![](https://habrastorage.org/getpro/habr/upload_files/0e5/904/5a3/0e59045a34597322edba81755d479fd0.png)
Как мы видим, мы можем создать другой изолят, а также общаться с ним, используя порты. Из того, что стоить запомнить: мы не можем вызывать нативный код в других изолятах, только в главных.
Внимание - спасибо за внимание :)
На этом у меня все, постарался рассказать все очень доступно. Спасибо всем, кто читал статью, а особенно тем, кто дочитал до конца. Это моя первая статья и я прошу дать мне фидбек :)
Комментарии (6)
YuliaEQ
02.03.2022 15:05Привет всем читателям! Ищу наставника по flutter разработке (2 -3 раза в неделю по 1-1,5ч), удаленно естественно) оплата до 2000р/ч, пишите в телеграм @juliaDGL
softkot
04.03.2022 11:08Из того, что стоить запомнить: мы не можем вызывать нативный код в других изолятах, только в главных.
Это утверждение в корне не верно. Натив можно а иногда и нужно вызывать из отдельного изолята.
vovaklh Автор
04.03.2022 11:58Я знаю, что есть костыли как это сделать, однако Вашим словам я верить не буду, киньте мне пример или ссылку на документацию) Я ж не просто эту инфу из головы взял, это было написано в другой статье на Хабре
softkot
04.03.2022 12:18Эти костыли имели место быть когда плагины подключались в первом варианте API, с появлением второй версии (https://docs.flutter.dev/development/packages-and-plugins/plugin-api-migration) ситуация сильно улучшилась и теперь все можно упростить до того что использовать flutter_isolate (https://github.com/rmawatson/flutter_isolate), который позволяет легко вызывать в изоляте код, который пользуется другими плагинами, которые в свою очередь пользуются нативом. Главное чтобы эти плагины были написаны в новом формате API.
anonymous