Lesson 6

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

Егор Долгов · 05.07.2019
This article was written for an older Flutter version and is being updated. Some code may not compile on the current version.

Программируя на 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 запросы.

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