Посмотрите на цикл событий Flutter

Раньше я ненавидел асинхронность во Flutter. Я имею в виду, если вы действительно думаете об этом, функция / метод, который является асинхронным, - это просто функция / метод, которые занимают действительно много времени. Так почему я должен использовать ключевое слово async? Почему я не могу просто нормально интегрировать асинхронный код с остальным кодом?
Я имею в виду, что если бы я сделал свой build() метод асинхронным, это, вероятно, было бы очень плохой идеей. Но то, что это плохая идея, не означает, что мне нельзя позволять это делать. Вот как ты учишься. Принимая плохие решения.
Так что я в значительной степени вырос из этой фазы. Асинхронность не идеальна во Flutter, и я объясню почему позже, но это не так плохо, как я думал.
Сначала немного основ - используйте линтер и используйте фьючерсы
Так что в первую очередь используйте линтер. И под этим я подразумеваю использование analysis_options.yaml. Вот один из репо Flutter. Хотя лично я считаю это слишком самоуверенным, поэтому я сделал свое здесь.
Потому что по умолчанию флаттер, или, лучше сказать, Дротик, позволяет вам уйти от самых безумных вещей. Раньше было хуже, когда это буквально позволяло вам ничего не возвращать из функции, которая явно должна что-то возвращать. Но нулевая безопасность исправила это.
Поэтому я всегда предполагаю, что Flutter сделает так, чтобы по умолчанию каждый проект создавался с analysis_options.yaml. Но пока этого не произошло.
Однако, возможно, к тому времени, когда вы это прочитаете, команда Flutter наконец-то решила добавить его. А пока сделайте analysis_options.yaml файл самостоятельно.
И причина, по которой я советую вам использовать линтер-файл, заключается, в частности, в одном правиле:
- avoid_void_async
Итак, в Dart вы можете использовать:
void foo() async {
// Do some stuff here
}
И это будет работать отлично. Но он возвращает void, что нормально, но для правильного использования await и async нам нужно вернуть Future следующим образом:
Future<void> foo() async {
// Do some stuff here
}
Это позволит нам делать все самое интересное с помощью async и await. Правило линтера автоматически предупредит вас, если вы случайно используете void.
Теперь вы думаете о будущем
Итак, теперь, когда мы используем фьючерсы, как нам на самом деле сделать с ними что-то полезное? Что ж, есть несколько способов их использования:
- Просто позвони им как обычно
- В ожидании будущего
- Цепные фьючерсы с использованием
.then()
1. Обращение к ним как обычно
analyticsInstance.logEvent(name: “All Your Base Are Belong To Us!”)
Технически это будущее, и оно будет реализовано в свое время.
Теперь это не многопоточность. Программисты, которые раньше не слышали об асинхронности, склонны считать, что все является многопоточным. Вместо этого Async использует цикл событий. Это видео объясняет это лучше всего (переходите примерно к 2:17).
Так что это не многопоточность. Это просто волшебство.
Конечно, в этом видео есть одно следствие. Мероприятие не может сделать слишком много работы сразу. В противном случае это приведет к засорению цикла событий, и ничего не произойдет.
Это особенно заметно, если у вас играет анимация. Это было большой проблемой в моих собственных приложениях. Мне пришлось использовать изоляты, чтобы справиться с этим - и даже тогда это не совсем гладко. Возможно, я расскажу о изолятах позже.
Также, когда он говорит: Несмотря на то, что Dart является однопоточным языком, он предлагает поддержку фьючерсов, потоков, фоновой работы и всего остального, что вам нужно писать современным, асинхронным и (в случае флаттера) реактивным способом. . в 0:14 - я могу представить, что он копается в Swift, который в то время не поддерживал асинхронность - но это только я.
2. Ожидание фьючерсов вручную - это то, что я использовал раньше
Это сложно, потому что паразитирует. Это означает, что если вы await используете асинхронную функцию или метод, функция или метод также должны быть async.
Итак, если у вас есть это:
void foo() {
someAsyncFunction();
}
Это хорошо. Но теперь допустим, что вы помешаны на контроле (как и я) и хотите точно знать, когда someAsyncFunction() закончится. Итак, вы используете await вот так:
void foo() {
await someAsyncFunction();
}
Но это выдаст вам ошибку. Потому что вам нужно ключевое слово async:
void foo() async {
await someAsyncFunction();
}
А также вы должны изменить тип возвращаемого значения на Future, как обсуждалось ранее:
Future<void> foo() async {
await someAsyncFunction();
}
Теперь это звучит не так уж плохо. Но потом вы понимаете, что если вы хотите, чтобы какой-то родитель ждал этой новой функции, он тоже должен использовать ключевое слово async. И он просто пузырится.
Как уже обсуждалось, я никогда толком не понимал, почему это было. Почему бы вам просто не добавить ключевое слово async, а ваша функция просто отнимет много времени?
И меня такой подход очень расстроил. Вот почему мне не нравились фьючерсы.
3. Пока я не понял, что вы можете использовать метод .then ()
И это просто волшебство. Допустим, у вас есть task1 и task2, где task1 асинхронный, а task2 зависит от task1. Затем, используя awaits, вам нужно будет пойти:
Future<void> foo() async {
await task1();
task2();
}
Это довольно неуклюже. Но допустим, вместо этого вы используете ключевое слово .then(), как показано ниже.
void foo() {
task1().then((_) {
task2();
});
}
Теперь foo() больше не должен быть асинхронным. Это довольно волшебно. И вдобавок ко всему, некоторые вещи, о которых вы даже не ожидали увидеть будущее, на самом деле таковы. Потому что будущее - это просто некоторая операция с начальной точкой и конечной точкой в будущем.
Например, показ диалога. Стандартный способ реализации такой:
showDialog( context: context, builder: (_) => /* ??? */, barrierDismissible: true, );
Но на самом деле у него есть начальная точка, когда вы запускаете код для отображения диалогового окна, и у него есть конечная точка, когда вы закрываете диалоговое окно. Так почему бы не реализовать это как будущее? Именно этим и занимается Flutter. И поскольку он реализован как будущее, вы можете использовать для него метод .then() следующим образом:
showDialog(
context: context,
builder: (_) => /* ??? */,
barrierDismissible: true,
).then((_) {
setState(() {
loading = false;
});
});
Это сообщает Flutter, что как только диалоговое окно закрывается, необходимо вызвать setState(), перестроить виджет и разрешить обновление любой анимации загрузки.
Я использую это все время. Другой способ использовать это - вызвать методы для объектов, которые, возможно, еще не инициализированы.
Так, например, у меня есть следующий код:
gamesServicesSignIn?.then((_) {
GamesServices.unlock(…)
.onError((Object error, StackTrace stack) {
crashlyticsRecordError(“Encountered errors in GameState 292: $error”, stack);
});
});
Это разблокирует достижение только тогда, когда GamesService был инициализирован, без необходимости вручную ждать будущего, чего вы, возможно, не захотите делать, если оно выдаст кучу ошибок.
Хотя .then() в некотором роде похож на await, поэтому вы должны делать это, чтобы должным образом подавлять ошибки.
gamesServicesSignIn = GamesServices.signIn().onError((_, __) {
gamesServicesSignIn = null;
});
Последние мысли
Итак, async не идеален. Меня больше всего беспокоит то, что все еще слишком легко забыть о вызове await.
Как и в Unity, если вы хотите запустить сопрограмму, вам нужно StartCoroutine(Foo()). Я имею в виду, что технически вы все еще можете пойти Foo(), но IDE укажет на это заранее.
Flutter этого не делает. Вероятно, потому что он не может определить, действительно ли вы хотели набрать Foo() или await Foo(). Это то, что мне действительно нравится в Dart. Что-то вроде runFuture(Foo()) и awaitFuture(Foo()). Он немного более неуклюжий, но более подробный.
Кроме этого, у меня нет никаких проблем с async. Я до сих пор не понимаю, зачем они нужны, но полагаю, что они говорят программисту: «Эй, послушайте, этот код может занять некоторое время для запуска». и позволить им запускать код, не дожидаясь его явного ожидания.
Так что они - мощный инструмент. Я имею в виду, что мне все еще больше нравится многопоточность, но я должен признать, что асинхронный код довольно полезен.