[Программирование, Разработка мобильных приложений, Dart, Flutter] Работа с асинхронностью в Dart
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет! Меня зовут Дмитрий Репин, я Flutter-разработчик в Surf.В этой статье я расскажу, как работать с асинхронностью в Dart: всё о самых важных классах библиотеки dart:async с примерами под катом. Поговорим о том, как в однопоточном языке сходить в сеть или базу данных и при этом не затормозить приложение. Эта статья написана по материалам моего ролика на YouTube. Посмотрите видео, если больше любите слушать, чем читать.Dart — однопоточный язык программирования, который выполняется в одном процессе. Что это значит по факту: если мы будем в одном потоке выполнять любую операцию, требующую времени, то приложение просто подвиснет. И всё время, пока мы, например, ждём ответа от сервера или выполнения запроса в БД, пользователь будет страдать и смотреть на лагающий интерфейс.К счастью, Dart — хитрый однопоточный язык, который предоставляет механизм Event Loop — он даёт возможность откладывать какие-то операции на потом, когда поток будет посвободнее. Такие отложенные операции мы будем называть асинхронными. Все операции в Dart можно разделить на два типа:
- Синхронные — те, что блокируют другие операции до своего выполнения.
- Асинхронные, которые позволяют другим операциям выполняться, пока текущая не закончится.
Вместе с операциями, функции тоже можно поделить на синхронные и асинхронные. И синхронные, и асинхронные функции могут содержать синхронные операции. Асинхронной функция начнёт считаться в тот момент, когда в ней появляется хотя бы одна асинхронная операция.Создание асинхронной функции с помощью класса FutureЧтобы было нагляднее, давайте напишем простую асинхронную функцию. Пусть она будет принимать строку и смотреть на её длину: если она больше 10 символов, функция вернёт число 42, в ином случае — ошибку. Представим, что вычисление длины строки — достаточно тяжеловесная операция чтобы сделать её асинхронной. Чтобы сделать код асинхронным, нам понадобится класс Future. В конструктор он принимает функцию, которую необходимо выполнить асинхронно, а также предоставляет два обработчика: then и catchError. Первый обрабатывает успешное выполнение функции, а второй — выполнение функции с ошибкой. В итоге у нас должен получиться такой код:
runSimpleFutureExample({
String question = 'В чем смысл жизни и всего такого?',
}) {
print('Start of future example');
Future(() {
print('Вопрос: $question');
if (question.length > 10) {
return 42;
} else {
throw Exception('Вы задали недостаточно сложный вопрос.');
}
}).then((result) {
print('Ответ: $result');
}).catchError((error) {
print('Ошибка. $error');
});
print('Finish of future example');
}
В начале и конце метода выводятся строки о том, что он начал и закончил работу, а между ними создаётся сам future. В конструктор он принимает метод, который вычисляет длину строки, а затем к Future добавляются обработчики then и catchError, которые обрабатывают выполнение Future. Попробуем запустить этот пример и посмотрим, в каком порядке выводится на консоль результат:Start of future exampleFinish of future exampleВопрос: В чем смысл жизни и всего такого?Ответ: 42В результате сначала выводятся строки о начале и завершении метода, а потом выполняется код самого Future. Дело в том, что асинхронный код в Dart выполняется исключительно после синхронного. Соответственно, в синхронном коде мы не можем обработать результат асинхронной операции — при работе с асинхронщиной стоит держать это в голове.Работать с Future через обработчики then и catchError — не самая хорошая идея, особенно если нужно передать результат одного Future во второй, а второго в третий и т. д., порождая callback hell. Для таких случаев Dart предоставляет ключевые слова async и await. Словом async помечается функция, исполняющая асинхронные операции через await. Словом await помечаются сами асинхронные операции. К сожалению, при таком подходе мы теряем обработчик catchError, однако никто не мешает нам обрабатывать ошибки через стандартный try/catch. Чтобы понять, как это работает, перепишем наш предыдущий пример с использованием async/await и для удобства вынесем Future в отдельный метод:
runFutureWithAwaitExample({
String question = 'В чем смысл жизни и всего такого?',
}) async {
print('Start of future example');
try {
final result = await _getAnswerForQuestion(
'В чем смысл жизни и всего такого?',
);
print('Ответ: $result');
} catch (error) {
print('Ошибка. $error');
}
print('Finish of future example');
}
Future<int> _getAnswerForQuestion(String question) => Future(() {
print('Вопрос: $question');
if (question.length > 10) {
return 42;
} else {
throw Exception('Вы задали недостаточно сложный вопрос.');
}
});
Здесь мы объявили метод, возвращающий Future. Вызываем его с использованием слова await, передавая в переменную result, с которой мы можем работать дальше, как будто в синхронном коде. Выведем на консоль результат работы метода:Start of future exampleВопрос: В чем смысл жизни и всего такого?Ответ: 42Finish of future exampleКак видим, теперь Future обрабатывается внутри метода, а не после его завершения. Но при этом не стоит забывать, что сам метод теперь стал асинхронным и выполнится только после выполнения синхронного кода.Обработка последовательности асинхронных событий с помощью StreamПомимо Future, есть ещё один важный класс для работы с асинхронностью — Stream. В отличие от Future, Stream может обрабатывать целый набор асинхронных событий. Давайте сразу разберём на примере:
void runStreamSimpleExample() {
print('Simple stream example started');
final stream = Stream.fromIterable([1, 2, 3, 4, 5]);
stream.listen((number) {
print('listener: $number');
});
print('Simple stream example finished');
}
У стрима много разных конструкторов, посмотрите их в документации. Здесь используем fromIterable. Создаём в массиве пять чисел, подписываемся на стрим через метод listen и выводим на консоль:Simple stream example startedSimple stream example finishedlistener: 1listener: 2listener: 3listener: 4listener: 5Как и в случае с Future, сначала выполняется синхронный код: сообщения о старте и завершения примера. Только потом обрабатывается стрим и выводятся на консоль числа. В этом примере подписка на стрим обрабатывается асинхронно через listen, однако есть способ обрабатывать стрим через знакомые нам ключевые слова async и await. Перепишем пример с их использованием:
void runStreamAwaitedSimpleExample() async {
print('Simple stream example with await started');
final stream = Stream.fromIterable([1, 2, 3, 4, 5]);
await for (final number in stream) {
print('Number: $number');
}
print('Simple stream example with await finished');
}
Теперь обработка стрима похожа на обычный цикл. Но не стоит обрабатывать слишком большие стримы таким образом: это может затормозить работу программы. Выведем результат на консоль:Simple stream example startedlistener: 1listener: 2listener: 3listener: 4listener: 5Simple stream example finishedТут всё так же, как и с async/await во future. Сама функция стала асинхронной, поэтому теперь обработка Stream происходит до завершения метода. Single-subscription и broadcast стримы. Основы StreamControllerПредставим, что нам нужно подписаться на стрим в двух разных местах программы. Для этого можно сделать какой-нибудь синглтон, который бы предоставлял стрим и который можно было бы дёргать в двух разных частях кода. Чтобы не усложнять, попробуем подписаться на стрим дважды в одном методе, и посмотрим, что получится:
print('Simple stream example started');
final stream = Stream.fromIterable([1, 2, 3, 4, 5]);
stream.listen((number) {
print('listener 1: $number');
});
stream.listen((number) {
print('listener 2: $number');
});
print('Simple stream example finished');
И результат:The following StateError was thrown while handling a gesture.Bad state: Stream has already been listened to.Мы получаем ошибку: на стрим уже произведена подписка и подписаться второй раз не выйдет. Здесь стоит рассказать о том, что существует два типа стримов: single-subscription и broadcast стримы. Мы создали single-subscription стрим. Такие стримы поставляют все данные подписчику разом и только после самой подписки. Так и происходит в нашем примере.Broadcast стримы отдают свои данные вне зависимости от того, подписан ли кто-нибудь на них или нет. При этом подписчики стрима получают события только с момента подписки, а не с момента старта жизни стрима. При создании стрима стоит посмотреть документацию к конструктору и разобраться, какие ограничения он накладывает и стрим какого типа создаётся через этот конструктор. Чтобы увидеть разницу, создадим broadcast стрим. В этом примере для создания стрима мы не будем использовать непосредственно сам Stream. Мы будем работать с классом StreamController. Он предоставляет доступ к самому стриму, даёт возможность управлять им и добавлять в него события.
///пример broadcast стрима
void runBroadcastStreamExample() {
print('Broadcast stream example started');
final streamController = StreamController.broadcast();
streamController.stream.listen((number) {
print('Listener 1: $number');
});
streamController.stream.listen((number) {
print('Listener 2: $number');
});
streamController.sink.add(1);
streamController.sink.add(2);
streamController.sink.add(3);
streamController.sink.add(4);
streamController.sink.add(5);
streamController.close();
print('Broadcast stream example finished');
}
Мы создали StreamController с broadcast стримом, дважды подписались на него и добавили через sink пять чисел, после чего закрыли стрим. Смотрим, что ушло на консоль:Broadcast stream example startedBroadcast stream example finishedListener 1: 1Listener 2: 1Listener 1: 2Listener 2: 2Listener 1: 3Listener 2: 3Listener 1: 4Listener 2: 4Listener 1: 5Listener 2: 5Проблем не возникло: два подписчика отработали, как и ожидалось. На самом деле у StreamController достаточно много функций, рекомендую почитать документацию к нему.Отписка от стрима с помощью StreamSubscriptionПомимо управления самим стримом часто возникает потребность управлять подпиской на него — для этого в dart:async добавлен StreamSubscription. Кстати, мы уже неявно работали с ним в наших предыдущих примерах, когда подписывались на стрим: на самом деле метод listen возвращает его, и таким образом мы можем выделить подписку в переменную. Модифицируем предыдущий пример так, чтобы вторая подписка отменялась после числа 3:
///пример broadcast стрима
void runBroadcastStreamExample() {
print('Broadcast stream example started');
final streamController = StreamController.broadcast();
streamController.stream.listen((number) {
print('Listener 1: $number');
});
StreamSubscription sub2;
sub2 = streamController.stream.listen((number) {
print('Listener 2: $number');
if (number == 3) {
sub2.cancel();
}
});
streamController.sink.add(1);
streamController.sink.add(2);
streamController.sink.add(3);
streamController.sink.add(4);
streamController.sink.add(5);
streamController.close();
print('Broadcast stream example finished');
}
Тут без сюрпризов, на консоль выводится то, что мы ожидали:Broadcast stream example startedBroadcast stream example finishedListener 1: 1Listener 2: 1Listener 1: 2Listener 2: 2Listener 1: 3Listener 2: 3Listener 1: 4Listener 1: 5Стоит отметить, что StreamSubscription не только даёт возможность отменять подписку полностью, но и приостанавливать и возобновлять её, а также предоставляет колбэки на события подписки. Как упростить работу с асинхронностьюМы разобрали все самые важные классы для dart:async, но осталось ещё три, о которых я бы хотел рассказать. Возможно, они используются не так часто, но могут быть очень полезны в определённых ситуациях.CompleterCompleter позволяет поставлять Future, отправлять событие о выполнении или событие об ошибке. Это может быть полезно, когда нужно сделать цепочку Future и вернуть результат. Разберём на примере. Пусть у нас будет completer, который складывает два числа с трёхсекундной задержкой. Это очень простой пример, но важно понимать, что тут можно сделать большую вложенность из Future и компактно уместить в один класс.
class CompleterTester {
void runCompleterInitTest() async {
print('Completer example started');
var sumCompleter = SumCompleter();
var sum = await sumCompleter.sum(20, 22);
print('Completer result: ' + sum.toString());
print('Completer example finished');
}
}
class SumCompleter {
Completer<int> completer = Completer();
Future<int> sum(int a, int b) {
_sumAsync(a, b);
return completer.future;
}
void _sumAsync(int a, int b) {
Future.delayed(Duration(seconds: 3), () {
return a + b;
}).then((value) {
completer.complete(value);
});
}
}
Разберём, что тут произошло. Мы создали класс SumCompleter и создали в нём completer. Затем объявили метод sum, который вызывает приватный метод на сложение и возвращает Future этого completer. Затем этот приватный метод выполняет операцию и вызывает метод complete у комплитера. После этого пользователь метода получает результат.Такой подход позволяет инкапсулировать в себе любую сложную часто используемую асинхронную логику и удобно с ней работать.StreamIteratorПредставьте, что вам нужно управлять переходом к следующему айтему стрима и делать это именно тогда, когда вам нужно. Именно это и делает StreamIterator. Он предоставляет метод moveNext для перехода к следующему элементу стрима. moveNext вернет true, если элемент пришел, и false, если стрим был закрыт. Также StreamIterator предоставляет свойство current для получения текущего элемента. Ну и конечно, метод cancel для отмены подписки. Небольшой пример по работе класса:
void runStreamIteratorExample() async {
print('StreamIteratorExample started');
var stream = Stream.fromIterable([1, 2, 3]);
var iterator = StreamIterator(stream);
bool moveResult;
do {
moveResult = await iterator.moveNext();
print('number: ${iterator.current}');
} while (moveResult);
print('StreamIteratorExample finished');
}
С использованием этого StreamIterator мы будем переходить к следующему числу только в то время, когда нам это удобно, выполнив перед этим все нужные действия. StreamTransformerНа самом деле это немного переусложнённая штука, но она отлично справляется, если вам нужно много раз трансформировать поток в приложении определённым образом. Самый очевидный пример — разделение файла на строки. Разберём на примере и напишем трансформер, который будет удваивать приходящее в него число:
void runStreamTransformerExample() async {
print('StreamTransformer example started');
StreamTransformer doubleTransformer =
new StreamTransformer.fromHandlers(handleData: (data, EventSink sink) {
sink.add(data * 2);
});
StreamController controller = StreamController();
controller.stream.transform(doubleTransformer).listen((data) {
print('data: $data');
});
controller.add(1);
controller.add(2);
controller.add(3);
print('StreamTransformer example finished');
}
В нашем случае он будет принимать на вход данные, которые нужно трансформировать, и sink, в который мы должны передать трансформированные данные. В теле метода просто удваиваем приходящее значение. Затем остаётся только трансформировать стрим с помощью метода transform. И вуаля: в методе listen значение удваивается. Обратите внимание: трансформер может и не отдать данные в синк, а может отдать два раза. То есть он работает не только как map или where, а наделён гораздо большей функциональностью.Рекомендую поиграться с этими примерами — они есть на нашем GitHub. Так вы точно поймете, как устроена асинхронность в Dart. Если что-то непонятно, задавайте вопросы — я отвечу в комментариях.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Машинное обучение] Распознавание речи с помощью инструментов машинного обучения
- [Разработка под iOS, Разработка мобильных приложений, Администрирование баз данных, Swift] Быстрый, простой, сложный: как мы выпилили Realm
- [Системное администрирование, Программирование, IT-инфраструктура, DevOps] Создание современных процессов CI/CD для бессерверных приложений с Red Hat OpenShift Pipelines и Argo CD. Часть 2 (перевод)
- [Программирование, Java, Микросервисы] Spring Cloud и Spring Boot. Часть 1: использование Eureka Server (перевод)
- [Программирование микроконтроллеров, Компьютерное железо, DIY или Сделай сам] Raspberry Pi Pico на МК RP2040: начало и первые шаги. Что есть поесть за $4
- [Разработка мобильных приложений, Разработка под Android, Flutter] Все, что вам нужно знать о переходе с Flutter страницы (перевод)
- [Разработка веб-сайтов, JavaScript, Программирование, ReactJS] Почему Context — это не инструмент «управления состоянием» (перевод)
- [Занимательные задачки, Программирование, Управление разработкой, Удалённая работа] Как отсеивать плохих программистов. 10 лучших автоматических инструментов проверки качества кода
- [Python, Программирование, Алгоритмы, Машинное обучение, Искусственный интеллект] Нейронная Сеть CLIP от OpenAI: Классификатор, который не нужно обучать. Да здравствует Обучение без Обучения
- [Программирование, Go, DevOps] Prometheus in Action: from default counters to SLO-related queries
Теги для поиска: #_programmirovanie (Программирование), #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_dart, #_flutter, #_flutter, #_dart, #_async/await, #_asinhronnost (асинхронность), #_surf, #_krossplatformennost (кроссплатформенность), #_krossplatformennaja_razrabotka (кроссплатформенная разработка), #_mobilnaja_razrabotka (мобильная разработка), #_blog_kompanii_surf (
Блог компании Surf
), #_programmirovanie (
Программирование
), #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
), #_dart, #_flutter
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:48
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет! Меня зовут Дмитрий Репин, я Flutter-разработчик в Surf.В этой статье я расскажу, как работать с асинхронностью в Dart: всё о самых важных классах библиотеки dart:async с примерами под катом. Поговорим о том, как в однопоточном языке сходить в сеть или базу данных и при этом не затормозить приложение. Эта статья написана по материалам моего ролика на YouTube. Посмотрите видео, если больше любите слушать, чем читать.Dart — однопоточный язык программирования, который выполняется в одном процессе. Что это значит по факту: если мы будем в одном потоке выполнять любую операцию, требующую времени, то приложение просто подвиснет. И всё время, пока мы, например, ждём ответа от сервера или выполнения запроса в БД, пользователь будет страдать и смотреть на лагающий интерфейс.К счастью, Dart — хитрый однопоточный язык, который предоставляет механизм Event Loop — он даёт возможность откладывать какие-то операции на потом, когда поток будет посвободнее. Такие отложенные операции мы будем называть асинхронными. Все операции в Dart можно разделить на два типа:
runSimpleFutureExample({
String question = 'В чем смысл жизни и всего такого?', }) { print('Start of future example'); Future(() { print('Вопрос: $question'); if (question.length > 10) { return 42; } else { throw Exception('Вы задали недостаточно сложный вопрос.'); } }).then((result) { print('Ответ: $result'); }).catchError((error) { print('Ошибка. $error'); }); print('Finish of future example'); } runFutureWithAwaitExample({
String question = 'В чем смысл жизни и всего такого?', }) async { print('Start of future example'); try { final result = await _getAnswerForQuestion( 'В чем смысл жизни и всего такого?', ); print('Ответ: $result'); } catch (error) { print('Ошибка. $error'); } print('Finish of future example'); } Future<int> _getAnswerForQuestion(String question) => Future(() { print('Вопрос: $question'); if (question.length > 10) { return 42; } else { throw Exception('Вы задали недостаточно сложный вопрос.'); } }); void runStreamSimpleExample() {
print('Simple stream example started'); final stream = Stream.fromIterable([1, 2, 3, 4, 5]); stream.listen((number) { print('listener: $number'); }); print('Simple stream example finished'); } void runStreamAwaitedSimpleExample() async {
print('Simple stream example with await started'); final stream = Stream.fromIterable([1, 2, 3, 4, 5]); await for (final number in stream) { print('Number: $number'); } print('Simple stream example with await finished'); } print('Simple stream example started');
final stream = Stream.fromIterable([1, 2, 3, 4, 5]); stream.listen((number) { print('listener 1: $number'); }); stream.listen((number) { print('listener 2: $number'); }); print('Simple stream example finished'); ///пример broadcast стрима
void runBroadcastStreamExample() { print('Broadcast stream example started'); final streamController = StreamController.broadcast(); streamController.stream.listen((number) { print('Listener 1: $number'); }); streamController.stream.listen((number) { print('Listener 2: $number'); }); streamController.sink.add(1); streamController.sink.add(2); streamController.sink.add(3); streamController.sink.add(4); streamController.sink.add(5); streamController.close(); print('Broadcast stream example finished'); } ///пример broadcast стрима
void runBroadcastStreamExample() { print('Broadcast stream example started'); final streamController = StreamController.broadcast(); streamController.stream.listen((number) { print('Listener 1: $number'); }); StreamSubscription sub2; sub2 = streamController.stream.listen((number) { print('Listener 2: $number'); if (number == 3) { sub2.cancel(); } }); streamController.sink.add(1); streamController.sink.add(2); streamController.sink.add(3); streamController.sink.add(4); streamController.sink.add(5); streamController.close(); print('Broadcast stream example finished'); } class CompleterTester {
void runCompleterInitTest() async { print('Completer example started'); var sumCompleter = SumCompleter(); var sum = await sumCompleter.sum(20, 22); print('Completer result: ' + sum.toString()); print('Completer example finished'); } } class SumCompleter { Completer<int> completer = Completer(); Future<int> sum(int a, int b) { _sumAsync(a, b); return completer.future; } void _sumAsync(int a, int b) { Future.delayed(Duration(seconds: 3), () { return a + b; }).then((value) { completer.complete(value); }); } } void runStreamIteratorExample() async {
print('StreamIteratorExample started'); var stream = Stream.fromIterable([1, 2, 3]); var iterator = StreamIterator(stream); bool moveResult; do { moveResult = await iterator.moveNext(); print('number: ${iterator.current}'); } while (moveResult); print('StreamIteratorExample finished'); } void runStreamTransformerExample() async {
print('StreamTransformer example started'); StreamTransformer doubleTransformer = new StreamTransformer.fromHandlers(handleData: (data, EventSink sink) { sink.add(data * 2); }); StreamController controller = StreamController(); controller.stream.transform(doubleTransformer).listen((data) { print('data: $data'); }); controller.add(1); controller.add(2); controller.add(3); print('StreamTransformer example finished'); } =========== Источник: habr.com =========== Похожие новости:
Блог компании Surf ), #_programmirovanie ( Программирование ), #_razrabotka_mobilnyh_prilozhenij ( Разработка мобильных приложений ), #_dart, #_flutter |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:48
Часовой пояс: UTC + 5