Урок 6

Урок 6. Асинхронные методы async и await

Егор Долгов · 05.07.2019
Статья написана для старой версии Flutter и сейчас обновляется. Часть кода может не компилироваться на актуальной версии.

Программируя на Flutter невозможно избежать использования асинхронных методов, таких как чтение данных из сети интернет, файлов и баз данных. Работа таких методов выводится в отдельные потоки и не нарушает работы основного потока программы: вычисление и отображение интерфейса. Одним словом, функции которые выполняются относительно долго — должны быть асинхронными.

В этом уроке мы научимся составлять и использовать такие методы. А уже в следующем начнем получать и обрабатывать данные из сети интернет.

Создание асинхронного метода на образе песочных часов

Создадим класс песочные часы, в которых есть 100 песчинок, и когда они в верхней части, значение sand=100 , когда все песчинки в нижней части — тогда sand=0

class SandGlass {
  int _sand = 0;
}

Добавим метод, который возвращает текущие состояние, он будет синхронным, т.е. по вызову он будет возвращать текущее значение приватной переменной sand .

time(){
  return _sand;
}

Добавим асинхронный метод tick , он будет «перемещать песчинки» из «верхней части» в нижнюю со скоростью 1/100 [песчинки/миллисекунды]. Чтобы метод был асинхронным, нужно указать модификатор async между сигнатурой функции и ее телом.

tick() async {
}

Еще можно написать эту функцию так:

Future tick() async {
}

Оба варианта дают одинаковый результат: вызывая асинхронную функцию мы всегда получаем в ответ Future — обещание.

Для проверки можно вывести в консоль любую асинхронную функцию и в выводе будет обещание Future : print('${tick()}');

Для работы с асинхронными функциями как с синхронными служит await — ожидание. await позволяет дождаться выполнения асинхронной функции и после обработать результат, если он есть.

Так например функция чтения из файла — асинхронная функция, которая возвращает ожидание, но нам нужен результат чтения — содержимое файла. Для этого перед функций чтения мы должны указать await .

await можно использовать только внутри асинхронных функциях, например в функции построения виджета так сделать нельзя:

Widget build(BuildContext context) {
	await clock.tick();// ошибка

Также мы не можем сделать build метод асинхронным, потому что возвращаемый тип должен быть Widget , и не может быть Future или Future<Widget> .

Но запустить асинхронный метод в build нам ничего не мешает:

Widget build(BuildContext context) {
	clock.tick();// запуск асинхронного метода

Когда мы пишем tick() async или Future tick() async — то результат тела функции всегда динамический. Но если результат функции должен быть типа bool , например условный оператор принимает только true или false :

if(await tick()) {
///...
} else {
///...
}

, нужно указать возвращаемый тип в скобках обещания:

Future<bool> tick() async

Полный листинг программы и детальное рассмотрение ее работы

import 'package:flutter/material.dart';

class SandGlass {
 int _sand = 0;

 time(){
   return _sand;
 }//time

 Future tick() async {
   _sand = 100;
   print('Start:tick');
   while(_sand > 0) {
     print('tick: _sand');
     _sand--;
     await new Future.delayed(const Duration(milliseconds: 100));
   }//while
   print('End:tick');
 }//tick
}//SandGlass

class MyApp extends StatefulWidget {
 @override
 State<StatefulWidget> createState() => MyAppState();
}

class MyAppState extends State {
 SandGlass clock = SandGlass();

 _reDrawWidget() async {
   if(clock.time() == 0) return;
   await new Future.delayed(const Duration(seconds: 1));
   setState(() {print('reDrawWidget()');});
 }

 @override
 void initState() {
   clock.tick();
   super.initState();
 }

 @override
 Widget build(BuildContext context) {
   _reDrawWidget();

   print('clock.tick: ${clock.time()}');

   return Center(child: Text('time is: ${clock.time()}'));
 }
}

void main() => runApp(new MaterialApp(home:Scaffold(body: MyApp())));
  1. Самая главная функция main , вызывает функцию runApp . Выполняется вызов очереди вложенных виджетов MaterialApp:home / Scaffold:body / MyApp > MyAppState
  2. Рассмотрим работу MyAppState:
  • В виджете есть параметр clock — экземпляр класса SandGlass .
  • Асинхронный метод reDrawWidget — вызывает перестроение виджета MyApp после задержки в 1 секунду.
  • Задержка происходит с помощью ожидания асинхронной функции await new Future.delayed(const Duration(seconds: 1)); не используйте синхронную функцию sleep для этих целей.
  • Метод initState переопределен, он вызывается один раз при построении виджета и инициализирует запуск песочных часов: clock.tick() .
  • Метод build запускает выполнение асинхронной функции reDrawWidget и выполняет вывод состояния песочных часов clock.time() в консоль и в текстовый виджет.
  1. Асинхронный метод tick в SandGlass :
  • задает значение sand = 100;
  • выполняет цикл while( sand > 0)
  • на каждой итерации цикла происходит уменьшение значения sand на единицу и задержка в 100 миллисекунд: sand--; await new Future.delayed(const Duration(milliseconds: 100));

На скриншоте вывода консоли, можно увидеть результаты параллельной работы асинхронных методов и основного потока.

На скриншоте вывода консоли, можно увидеть результаты параллельной работы асинхронных методов и основного потока.
На скриншоте вывода консоли, можно увидеть результаты параллельной работы асинхронных методов и основного потока.

Асинхронные методы занимают важное место в Dart и Flutter, в следующем уроке мы рассмотрим http запросы.

Демонстрация работы приложения с асинхронным методом
Демонстрация работы приложения с асинхронным методом