阅读 2134

Flutter学习计划(二) - Dart中的Future、async和await

Dart是一门单线程的语言,我们在开发进行耗时操作(比如网络请求、数据库存储、文件读取)时会阻塞我们的程序.Dart的并发机制可以在等待一个操作完成的同时进行别的操作。 在Dart中执行异步操作,可以用Future类和asyncawait关键字;

一、Future 的基本用法

Future<T> 类,其表示一个T类型的异步操作结果。如果异步操作不需要结果,则类型为 Future<void>。也就是说首先 Future 是个泛型类,可以指定类型。如果没有指定相应类型的话,则Future会在执行动态的推导类型。

1.1 Future的基本用法

Future是一种工厂构造函数。Future的工厂构造函数,创建一个最简单的Future

factory Future(FutureOr<T> computation()) {
  _Future<T> result = new _Future<T>();
  Timer.run(() {
    try {
      result._complete(computation());
    } catch (e, s) {
      _completeWithErrorCallback(result, e, s);
    }
  });
  return result;
}
复制代码

什么是工厂构造函数?

  • 与普通构造函数不同,工厂函数不会自动生成实例,而是通过代码来决定返回的实例对象。
  • 在Dart中,工厂构造函数的关键字为factory
  • 我们知道,构造函数包含类名构造函数和命名构造方法,在构造方法前加上factory之后变成了工厂构造函数。也就是说factory可以放在类名函数之前,也可以放在命名函数之前。

我们通过Future的工厂构造函数创建一个最简单的Future

String _data = '0';

void main() {
  getData();
  print('4-做其他事');
}

void getData() {
  print('1-开始data=$_data');

  Future(() {
    for (int i = 0; i < 100000000; i++) { // 模拟耗时操作
      _data = '网络数据'; 
    }
    print('2-结束data=$_data');
  });

  print('3-结束data=$_data');
}

运行结果:
flutter: 1-开始data=0
flutter: 3-结束data=0
flutter: 4-做其他事
flutter: 2-结束data=网络数据
复制代码

1.2 async、await的基本用法

关键字asyncawait是Dart语言异步支持的一部分。

  • async:用来表示函数是异步的,定义的函数会返回一个 Future 对象。
  • await:后面跟着一个 Future,表示等待该异步任务完成后才会继续往下执行。await只能出现在异步函数内部,能够让我们可以像写同步代码那样来执行异步任务而不使用回调的方式。

await关键字使用必须满足两个条件:

  • 当前函数必须是异步函数(即在函数头中包含关键字async的函数);
  • await修饰的任务必须是异步任务
String _data = '0';

void main() {
  getData2();
  print('4-做其他事');
}

void getData2() async {
  print('1-开始data=$_data');

  // 1.后面的操作必须是异步才能用await
  // 2。当前函数必须是异步函数
  await Future(() {
    for (int i = 0; i < 100000000; i++) {
      _data = '网络数据';
    }
    print('2-结束data=$_data');
  });

  print('3-结束data=$_data');
}

运行结果:
flutter: 1-开始data=0
flutter: 4-做其他事
flutter: 2-结束data=网络数据
flutter: 3-结束data=网络数据
复制代码
  1. getData2()被async关键词修饰,变为异步函数。 所以任务4先执行.
  2. 任务2await关键词修饰,等待该异步任务完成后才会继续往下执行任务3

1.3 Future.value()

创建一个返回指定value值的Future,并且返回Future。

void main() {
  futureValueTest();
  print('4-做其他事情');
}

void futureValueTest() async {
  var future = await Future.value(1);
  print(future);
}

运行结果:
flutter: 4-做其他事情
flutter: 1
复制代码

1.4 Future.delay()

创建一个延迟执行的Future,并且返回Future对象。

void main() {
  futterDelayTest();
  print('4-做其他事情');
}

void futterDelayTest() {
  Future.delayed(Duration(seconds: 3), () {
    print("延时3秒执行");
  });
}

运行结果:
flutter: 4-做其他事情
flutter: 延时3秒执行
复制代码

Future中实现的延时操作通过Timer来实现的,在实际开发中,如果只是一个单纯的延时操作,建议使用Timer,

void main() {
  timerTest();
  print('4-做其他事情');
}

void timerTest() {
  Timer timer = new Timer(Duration(seconds: 3), () {
    print("延时3秒执行");
  });
}

运行结果:
flutter: 4-做其他事情
flutter: 延时3秒执行
复制代码

二、Future的结果处理

对于 Future 来说,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。

  • 一个 Future 只会对应一个结果,要么成功,要么失败。
  • Future 的所有API的返回值仍然是一个 Future 对象,所以可以很方便的进行链式调用。

Dart提供了下面三个方法用来处理Future的结果。

Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
Future<T> catchError(Function onError, {bool test(Object error)});
Future<T> whenComplete(FutureOr action());
复制代码

2.1 Future.then()

用来注册一个 Future 完成时要调用的回调,并且返回一个Future对象。

  • 如果 Future 有多个then(),它们也会按照链接的先后顺序同步执行,并共用一个event loop
  • then()比Future默认的队列优先级高,then()会在 Future函数体执行完毕后立刻执行
void main() {
  Future(() => print('A')).then((value) => print('A结束'));
  Future(() => print('B')).then((value) => print('B结束'));
}

运行结果:
flutter: A
flutter: A结束
flutter: B
flutter: B结束
复制代码

2.2 Future.catchError()

用来注册一个捕捉 Future 的错误的回调,并且返回一个 Future 对象:

2.2.1 then() 在 catchError() 前使用

String _data = '0';

void main() {
  getData3();
  print('4-做其他事情');
}

void getData3() async {
  print('1-开始data=$_data');
  
  Future(() {
    for (int i = 0; i < 100000000; i++) {
      _data = '网络数据';
      throw Exception('网络异常');
    }
    print('1-结束data=$_data');
  }).then((value) {
    print('处理业务');
  }).catchError((e) {
    print('捕获异常');
  });

  print('2-结束data=$_data');
}

运行结果:
flutter: 1-开始data=0
flutter: 2-结束data=0
flutter: 4-做其他事情
flutter: 捕获异常
复制代码

2.2.2 then() 在 catchError() 后使用

String _data = '0';

void main() {
  getData3();
  print('4-做其他事情');
}

void getData4() async {
  print('1-开始data=$_data');
  
  Future(() {
    for (int i = 0; i < 100000000; i++) {
      _data = '网络数据';
      throw Exception('网络异常');
    }
    print('1-结束data=$_data');
  }).catchError((e) {
    print('捕获异常');
  }).then((value) {
    print('处理业务');
  });

  print('2-结束data=$_data');
}

运行结果:
flutter: 1-开始data=0
flutter: 2-结束data=0
flutter: 4-做其他事情
flutter: 捕获异常
flutter: 处理业务
复制代码

比较下两个结果:then()catchError() 后使用,会走处理业务的回调。

2.2.3 then()中回调onError和Future.catchError

Future.catchError回调只处理原始 Future 抛出的错误,不能处理回调函数抛出的错误,此时可以使用then()的回调onError

void main() {
  getData5();
  print('4-做其他事情');
}

void getData5() async {
  Future(() {
    throw Exception('error1');
  }).catchError((e) {
    print(e);
    throw Exception('error2');
  }).then(print, onError: (error) {
    print(error);
  });
}

运行结果:
flutter: 4-做其他事情
flutter: Exception: error1
flutter: Exception: error2
复制代码

2.3 Future.whenComplete()

Future 完成之后总是会调用,不管是错误导致的完成还是正常执行完毕,并且返回一个 Future 对象:

void main() {
  Future(() {
    throw '发生错误';
  }).then(print).catchError(print).whenComplete(() => print('whenComplete'));

  Future(() {
    return '没有错误';
  }).then(print).catchError(print).whenComplete(() => print('whenComplete'));
}

运行结果:
flutter: 发生错误
flutter: whenComplete
flutter: 没有错误
flutter: whenComplete
复制代码

三、Future的高级用法

3.1 Future.wait()

开发中会遇到这样的场景:网络请求A和网络请求B都完成以后,再执行代码C,此时可以使用Future.wait()

Future.wait()会等待多个Future完成,并收集它们的结果。有两种情况:

  1. 所有 Future 都有正常结果返回。则Future的返回结果是所有指定Future的结果的集合
void main() {
  futureWaitTest();
  print('4-做其他事情');
}

void futureWaitTest() {
  var future1 = new Future(() => '任务1');
  var future2 = new Future(() => '任务2');
  var future3 = new Future(() => '任务3');
  Future.wait([future1, future2, future3]).then(print).catchError(print);
  print('任务添加完毕');
}

运行结果:
flutter: 任务添加完毕
flutter: 4-做其他事情
flutter: [任务1, 任务2, 任务3]
复制代码
  1. 其中一个或者几个 Future 发生错误,产生了error。则 Future 的返回结果是第一个发生错误的 Future 的值:
void main() {
  futureWaitTest();
  print('4-做其他事情');
}

void futureWaitTest() {
  var future1 = new Future(() => '任务1');
  var future2 = new Future(() => throw throw Exception('任务2异常'));
  var future3 = new Future(() => '任务3');
  Future.wait([future1, future2, future3]).then(print).catchError(print);
  print('任务添加完毕');
}

运行结果:
flutter: 任务添加完毕
flutter: 4-做其他事情
flutter: Exception: 任务2异常
复制代码

3.2 Future.timeout()

开发中会遇到这样的场景:网络请求A超过30秒后抛出超时异常,此时可以使用Future.timeout()

void main() {
  futureTimeoutTest();
  print('4-做其他事情');
}

void futureTimeoutTest() {
  Future.delayed(Duration(seconds: 5), () {
    return '网络请求5秒';
  }).timeout(Duration(seconds: 3)).then(print).catchError(print);
}

运行结果:
flutter: 4-做其他事情
flutter: TimeoutException after 0:00:03.000000: Future not completed
复制代码

四、Dart的事件循环 - event loop

在Dart中,实际上有两种队列

  • 事件队列(event queue):包含所有的外来事件:I/O、mouse events、drawing events、timers、isolate之间的信息传递。(Dart为 event queue 的任务建立提供了一层封装,就是我们常用到的Future)
  • 微任务队列(microtask queue):主要是由Dart内部产生,表示一个短时间内就会完成的异步任务。优先级最高。

事件循环

正常情况下,一个 Future 异步任务的执行是相对简单的:

  1. 声明一个 Future 时,Dart会将异步任务的函数执行体放入event queue,然后立即返回,后续的代码继续同步执行;
  2. 当同步执行的代码执行完毕后,event queue会按照加入event queue的顺序(即声明顺序),依次取出事件,最后同步执行 Future 的函数体及后续的操作。
  1. 微任务队列优先级最高,会优先执行,只要队列中还有任务,就可以一直霸占着事件循环。
  2. 如果有太多的微任务队列存在,可能会对事件队列中的触摸、绘制等外部事件造成一定的阻塞
  3. then()也是添加进微任务队列中

我们看个例子,加深下对event loop的理解:

请问下段代码的打印顺序

void main() {
  futureQueueTest();
}

void futureQueueTest() {
  Future future1 = Future(() => null);
  future1.then((value) {
    print('6');
    scheduleMicrotask(() => print(7));
  }).then((value) => print('8'));
  Future future2 = Future(() => print('1'));
  Future(() => print('2'));
  scheduleMicrotask(() => print('3'));
  future2.then((value) => print('4'));

  print('5');
}
复制代码
点击查看运行结果:
 
flutter: 5
flutter: 3
flutter: 6
flutter: 8
flutter: 7
flutter: 1
flutter: 4
flutter: 2
复制代码
  1. 声明 Future 时,先将它们的异步函数执行体放入 event queue ,然后立即返回,后续代码继续同步执行。所以先打印5
  2. event loop循环 先选择队列优先级高的微任务3执行,打印3
  3. event queue 按照加入event queue的顺序(即声明顺序),依次取出事件,最后同步执行future1future2的函数体及后续的操作。所以再打印6, 并将新声明的微任务7加入 event loop循环
  4. future1的函数体结束后,优先执行future1的后续操作then()。所以再打印8;
  5. 此时future1函数体及后续的操作全部执行完毕,再次 event loop循环 选择队列优先级高的微任务7执行,打印7
  6. 再次 event loop循环 ,执行future2的函数体及后续的操作,打印14
  7. 再次 event loop循环 ,执行future的函数体及后续的操作,打印2

参考资料

Dart中的异步编程——Future、async和await

文章分类
iOS