Flutter异步编程-Future

2,324 阅读14分钟

Future可以说是在Dart异步编程中随处可见,比如一个网络请求返回的是Future对象,或者访问一个SharedPreferences返回一个Future对象等等。异步操作可以让你的程序在等待一个操作完成时继续处理其它的工作。而Dart 使用 Future 对象来表示异步操作的结果。我们通过前面文章都知道Dart是一个单线程模型的语言,所以遇到延迟的计算一般都需要采用异步方式来实现,那么 Future 就是代表这个异步返回的结果。

1. 复习几个概念

1.1 事件循环EventLoop

Dart的EventLoop事件循环和Javascript很类似,同样循环中拥有两个FIFO队列: 一个是事件队列(Event Queue),另一个就是微任务队列(MicroTask Queue)。

  • Event Queue主要包含IO、手势、绘制、定时器(Timer)、Stream流以及本文所讲Future等
  • MicroTask Queue主要包含Dart内部的微任务(内部非常短暂操作),一般是通过 scheduleMicroTask 方法实现调度,它的优先级比Event Queue要高

1.2 事件循环执行的流程

  • 1、初始化两个队列,分别是事件队列和微任务队列
  • 2、执行 main 方法
  • 3、启动事件循环EventLoop

注意:当事件循环正在处理microtask的时候,event queue会被堵塞。

2. 为什么需要Future

我们都知道Dart是单线程模型语言,如果需要执行一些延迟的操作或者IO操作,默认采用单线程同步的方式就有可能会造成主isolate阻塞,从而导致渲染卡顿。基于这一点我们需要实现单线程的异步方式,基于Dart内置的非阻塞API实现,那么其中Future就是异步中非常重要的角色。Future表示异步返回的结果,当执行一个异步延迟的计算时候,首先会返回一个Future结果,后续代码可以继续执行不会阻塞主isolate,当Future中的计算结果到达时,如果注册了 then 函数回调,对于返回成功的回调就会拿到最终计算的值,对于返回失败的回调就会拿到一个异常信息

可能很多人有点疑惑,实现异步使用isolate就可以了,为什么还需要Future? 关于这点前面文章也有说过,Future相对于isolate来说更轻量级,而且创建过多的isolate对系统资源也是非常大的开销。熟悉isolate源码小伙伴就知道,实际上isolate映射对应操作系统的OSThread. 所以对于一些延迟性不高的还是建议使用Future来替代isolate.

可能很多人还是有疑惑,关于实现异步Dart中的async和await也能实现异步,为什么还需要Future? Future相对于async, await的最大优势在于它提供了强大的链式调用,链式调用优势在于可以明确代码执行前后依赖关系以及实现异常的捕获。 一起来看个常见的场景例子,比如需要请求book详情时,需要先请求拿到对应book id, 也就是两个请求有前后依赖关系的,这时候如果使用async,await可能就不如future来的灵活。

  • 使用async和await实现:
_fetchBookId() async {
  //request book id
}

_fetchBookDetail() async {
  //需要在_fetchBookDetail内部去await取到bookId,所以要想先执行_fetchBookId再执行_fetchBookDetail,
  //必须在_fetchBookDetail中await _fetchBookId(),这样就会存在一个问题,_fetchBookDetail内部耦合_fetchBookId
  //一旦_fetchBookId有所改动,在_fetchBookDetail内部也要做相应的修改
  var bookId = await _fetchBookId();
  //get bookId then request book detail
}

void main() async {
   var bookDetail = await _fetchBookDetail();// 最后在main函数中请求_fetchBookDetail
}

//还有就是异常捕获
_fetchDataA() async {
  try {
    //request data
  } on Exception{
    // do sth
  } finally {
    // do sth
  }
}

_fetchDataB() async {
  try {
    //request data
  } on Exception{
    // do sth
  } finally {
    // do sth
  }
}
void main() async {
  //防止异常崩溃需要在每个方法内部都要添加try-catch捕获
  var resultA = await _fetchDataA();
  var resultB = await _fetchDataB();
}
  • Future的实现
_fetchBookId() {
  //request book id
}

_fetchBookDetail() {
  //get bookId then request book detail
}

void main() {
   Future(_fetchBookId()).then((bookId) => _fetchBookDetail());
  //或者下面这种方式
   Future(_fetchBookId()).then((bookId) => Future(_fetchBookDetail()));
}

//捕获异常的实现
_fetchBookId() {
  //request book id
}

_fetchBookDetail() {
  //get bookId then request book detail
}

void main() {
  Future(_fetchBookId())
      .catchError((e) => '_fetchBookId is error $e')
      .then((bookId) => _fetchBookDetail())
      .catchError((e) => '_fetchBookDetail is error $e');
}

总结一下,为什么需要Future的三个点:

  • 在Dart单线程模型,Future作为异步的返回结果是Dart异步中不可或缺的一部分。
  • 在大部分Dart或者Flutter业务场景下,Future相比isolate实现异步更加轻量级,更加高效。
  • 在一些特殊场景下,Future相比async, await在链式调用上更有优势。

3. 什么是Future

3.1 官方描述

用专业术语来说future 是 Future<T> 类的对象,其表示一个 T 类型的异步操作结果。如果异步操作不需要结果,则 future 的类型可为 Future<void>。当一个返回 future 对象的函数被调用时,会发生两件事:

  • 将函数操作列入队列等待执行并返回一个未完成的 Future 对象。
  • 不久后当函数操作执行完成,Future 对象变为完成并携带一个值或一个错误。

3.2 个人理解

其实我们可以把Future理解为一个装有数据的“盒子”,一开始执行一个异步请求会返回一个Future“盒子”,然后就接着继续下面的其他代码。等到过了一会异步请求结果返回时,这个Future“盒子”就会打开,里面就装着请求结果的值或者是请求异常。那么这个Future就会有三种状态分别是: 未完成的状态(Uncompleted), “盒子”处于关闭状态;完成带有值的状态(Completed with a value), “盒子”打开并且正常返回结果状态;完成带有异常的状态(Completed with a error), “盒子”打开并且失败返回异常状态;

下面用一个Flutter的例子来结合EventLoop理解下Future的过程,这里有个按钮点击后会去请求一张网络图片,然后把这张图片显示出来。

RaisedButton(
  onPressed: () {
    final myFuture = http.get('https://my.image.url');//返回一个Future对象
    myFuture.then((resp) {//注册then事件
      setImage(resp);
    });
  },
  child: Text('Click me!'),
)
  • 首先,当点击 RaisedButton 的时候,会传递一个 Tap 事件到事件队列中(因为系统手势属于Event Queue),然后 Tap 事件交由给事件循环EventLoop处理。

image.png

  • 然后,事件循环会处理 Tap 事件最终会触发执行 onPressed 方法,随即就会利用http库发出一个网络请求,并且就会返回一个Future, 这时候你就拿到这个数据“盒子”,但是这时候它的状态是关闭的也就是uncompleted状态。然后再用 then 注册当数据“盒子”打开时候的回调。这时候 onPressed 方法就执行完毕了,然后就是一直等着,等着HTTP请求返回的图片数据,这时候整个事件循环不断运行在处理其他的事件。

image.png

  • 最终,HTTP请求的图片数据到达了,这时候Future就会把真正的图片数据装入到“盒子”中并且把盒子打开,然后注册的 then 回调方法就会被触发,拿到图片数据并把图片显示出来。

image.png

4. Future的状态

通过上面理解Future总共有3种状态分别是: 未完成的状态(Uncompleted), “盒子”处于关闭状态;完成带有值的状态(Completed with a value), “盒子”打开并且正常返回结果状态;完成带有异常的状态(Completed with a error), “盒子”打开并且失败返回异常状态. 

实际上从Future源码角度分析,总共有5种状态:

  • __stateIncomplete : _初始未完成状态,等待一个结果
  • __statePendingComplete: _Pending等待完成状态, 表示Future对象的计算过程仍在执行中,这个时候还没有可以用的result.
  • __stateChained: _链接状态(一般出现于当前Future与其他Future链接在一起时,其他Future的result就变成当前Future的result)
  • __stateValue: _完成带有值的状态
  • __stateError: _完成带有异常的状态
class _Future<T> implements Future<T> {
  /// Initial state, waiting for a result. In this state, the
  /// [resultOrListeners] field holds a single-linked list of
  /// [_FutureListener] listeners.
  static const int _stateIncomplete = 0;

  /// Pending completion. Set when completed using [_asyncComplete] or
  /// [_asyncCompleteError]. It is an error to try to complete it again.
  /// [resultOrListeners] holds listeners.
  static const int _statePendingComplete = 1;

  /// The future has been chained to another future. The result of that
  /// other future becomes the result of this future as well.
  /// [resultOrListeners] contains the source future.
  static const int _stateChained = 2;

  /// The future has been completed with a value result.
  static const int _stateValue = 4;

  /// The future has been completed with an error result.
  static const int _stateError = 8;

  /** Whether the future is complete, and as what. */
  int _state = _stateIncomplete;
  ...
}

5. 如何使用Future

5.1 Future的基本使用

  • 1. factory Future(FutureOr computation())

Future的简单创建就可以通过它的构造函数来创建,通过传入一个异步执行Function.

//Future的factory构造函数
factory Future(FutureOr<T> computation()) {
  _Future<T> result = new _Future<T>();
  Timer.run(() {//内部创建了一个Timer
    try {
      result._complete(computation());
    } catch (e, s) {
      _completeWithErrorCallback(result, e, s);
    }
  });
  return result;
}
void main() {
  print('main is executed start');
  var function = () {
    print('future is executed');
  };
  Future(function);
  print('main is executed end');
}
//或者直接传入一个匿名函数
void main() {
  print('main is executed start');
  var future = Future(() {
    print('future is executed');
  });
  print('main is executed end');
}

输出结果: image.png 从输出结果可以发现Future输出是一个异步的过程,所以 future is executed 输出在 main is executed end 输出之后。这是因为main方法中的普通代码都是同步执行的,所以是先把main方法中 main is executed start 和 main is executed end 输出,等到main方法执行结束后就会开始检查 MicroTask 队列中是否存在task, 如果有就去执行,直到检查到 MicroTask Queue 为空,那么就会去检查 Event Queue ,由于Future的本质是在内部开启了一个 Timer 实现异步,最终这个异步事件是会放入到 Event Queue 中的,此时正好检查到当前 Future ,这时候的Event Loop就会去处理执行这个 Future 。所以最后输出了 future is executed .

  • 2. Future.value()

创建一个返回指定value值的Future对象, 注意在value内部实际上实现异步是通过 scheduleMicrotask . 之前文章也说过实现Future异步方式无非只有两种,一种是使用 Timer 另一种就是使用scheduleMicrotask

void main() {
  var commonFuture = Future((){
    print('future is executed');
  });
  var valueFuture = Future.value(100.0);//
  valueFuture.then((value) => print(value));
  print(valueFuture is Future<double>);
}

输出结果: image.png 通过上述输出结果可以发现,true先输出来是因为它是同步执行的,也说明最开始同步执行拿到了 valueFuture . 但是为什么 commonFuture 执行在 valueFuture 执行之后呢,这是因为 Future.value 内部实际上是通过 scheduleMicrotask 实现异步的,那么就不难理解了等到main方法执行结束后就会开始检查 MicroTask 队列中是否存在task,正好此时的 valueFuture 就是那么就先执行它,直到检查到 MicroTask Queue 为空,那么就会去检查 Event Queue ,由于Future的本质是在内部开启了一个 Timer 实现异步,最终这个异步事件是会放入到 Event Queue 中的,此时正好检查到当前 Future ,这时候的Event Loop就会去处理执行这个 Future

不妨来看下 Future.value 源码实现:

  factory Future.value([FutureOr<T> value]) {
    return new _Future<T>.immediate(value);//实际上是调用了_Future的immediate方法
  }

//进入immediate方法
 _Future.immediate(FutureOr<T> result) : _zone = Zone.current {
    _asyncComplete(result);
  }
//然后再执行_asyncComplete方法
 void _asyncComplete(FutureOr<T> value) {
    assert(!_isComplete);
    if (value is Future<T>) {//如果value是一个Future就把它和当前Future链接起来,很明显100这个值不是
      _chainFuture(value);
      return;
    }
    _setPendingComplete();//设置PendingComplete状态
    _zone.scheduleMicrotask(() {//注意了:这里就是调用了scheduleMicrotask方法
      _completeWithValue(value);//最后通过_completeWithValue方法回调传入value值
    });
  }
  • 3. Future.delayed()

创建一个延迟执行的future。实际上内部就是通过创建一个延迟的 Timer 来实现延迟异步操作。  Future.delayed 主要传入两个参数一个是 Duration 延迟时长,另一个就是异步执行的Function。

void main() {
  var delayedFuture = Future.delayed(Duration(seconds: 3), (){//延迟3s
    print('this is delayed future');
  });
  print('main is executed, waiting a delayed output....');
}

Future.delayed 方法就是使用了一个延迟的 Timer 实现的,具体可以看看源码实现:

factory Future.delayed(Duration duration, [FutureOr<T> computation()]) {
    _Future<T> result = new _Future<T>();
    new Timer(duration, () {//创建一个延迟的Timer,传递到Event Queue中,最终被EventLoop处理
      if (computation == null) {
        result._complete(null);
      } else {
        try {
          result._complete(computation());
        } catch (e, s) {
          _completeWithErrorCallback(result, e, s);
        }
      }
    });
    return result;
  }

5.2 Future的进阶使用

  • 1. Future的forEach方法

forEach方法就是根据某个集合,创建一系列的Future,然后再按照创建的顺序执行这些Future. Future.forEach有两个参数:一个是 Iterable 集合对象,另一个就是带有迭代元素参数的Function方法

void main() {
  var futureList = Future.forEach([1, 2, 3, 4, 5], (int element){
    return Future.delayed(Duration(seconds: element), () => print('this is $element'));//每隔1s输出this is 1, 隔2s输出this is 2, 隔3s输出this is 3, ...
  });
}

输出结果: image.png

  • 2. Future的any方法

Future的any方法返回的是第一个执行完成的future的结果,不管是否正常返回还是返回一个error.

void main() {
  var futureList = Future.any([3, 4, 1, 2, 5].map((delay) =>
          new Future.delayed(new Duration(seconds: delay), () => delay)))
      .then(print)
      .catchError(print);
}

输出结果: image.png

  • 3. Future的doWhile方法

Future.doWhile方法就是重复性地执行某一个动作,直到返回false或者Future,退出循环。特别适合于一些递归请求子类目的数据。

void main() {
  var totalDelay = 0;
  var delay = 0;

  Future.doWhile(() {
    if (totalDelay > 10) {//超过10s跳出循环
      print('total delay: $totalDelay s');
      return false;
    }
    delay += 1;
    totalDelay = totalDelay + delay;
    return new Future.delayed(new Duration(seconds: delay), () {
      print('wait $delay s');
      return true;
    });
  });
}

输出结果:

  • 4. Future的wait方法

用来等待多个future完成,并整合它们的结果,有点类似于RxJava中的zip操作。那这样的结果就有两种:

  • 若所有future都有正常结果返回:则future的返回结果是所有指定future的结果的集合
  • 若其中一个future有error返回:则future的返回结果是第一个error的值
void main() {
  var requestApi1 = Future.delayed(Duration(seconds: 1), () => 15650);
  var requestApi2 = Future.delayed(Duration(seconds: 2), () => 2340);
  var requestApi3 = Future.delayed(Duration(seconds: 1), () => 130);

  Future.wait({requestApi1, requestApi2, requestApi3})
      .then((List<int> value) => {
        //最后将拿到的结果累加求和
        print('${value.reduce((value, element) => value + element)}')
      });
}

输出结果: image.png

//异常处理
void main() {
  var requestApi1 = Future.delayed(Duration(seconds: 1), () => 15650);
  var requestApi2 = Future.delayed(Duration(seconds: 2), () => throw Exception('api2 is error'));
  var requestApi3 = Future.delayed(Duration(seconds: 1), () => throw Exception('api3 is error'));//输出结果是api3 is error这是因为api3先执行,因为api2延迟2s

  Future.wait({requestApi1, requestApi2, requestApi3})
      .then((List<int> value) => {
        //最后将拿到的结果累加求和
        print('${value.reduce((value, element) => value + element)}')
      });
}

输出结果: image.png

  • 5. Future的microtask方法

我们都知道Future一般都是会把事件加入到Event Queue中,但是Future.microtask方法提供一种方式将事件加入到Microtask Queue中,创建一个在microtask队列运行的future。 在上面讲过,microtask队列的优先级是比event队列高的,而一般future是在event队列执行的,所以Future.microtask创建的future会优先于其他future进行执行。

void main() {
  var commonFuture = Future(() {
    print('common future is executed');
  });
  var microtaskFuture = Future.microtask(() => print('microtask future is executed'));
}

输出结果: image.png

  • 6. Future的sync方法

Future.sync方法返回的是一个同步的Future, 但是需要注意的是,如果这个Future使用 then 注册Future的结果就是一个异步,它会把这个Future加入到MicroTask Queue中。

void main () {
  Future.sync(() => print('sync is executed!'));
  print('main is executed!');
}

输出结果: image.png 如果使用then来注册监听Future的结果,那么就是异步的就会把这个Future加入到MicroTask Queue中。

void main() {
  Future.delayed(Duration(seconds: 1), () => print('this is delayed future'));//普通Future会加入Event Queue中
  Future.sync(() => 100).then(print);//sync的Future需要加入microtask Queue中
  print('main is executed!');
}

image.png

5.3 处理Future返回的结果

  • 1. Future.then方法

Future一般使用 then 方法来注册Future回调,需要注意的Future返回的也是一个Future对象,所以可以使用链式调用使用Future。这样就可以将前一个Future的输出结果作为后一个Future的输入,可以写成链式调用。

void main() {
  Future.delayed(Duration(seconds: 1), () => 100)
      .then((value) => Future.delayed(Duration(seconds: 1), () => 100 + value))
      .then((value) => Future.delayed(Duration(seconds: 1), () => 100 + value))
      .then((value) => Future.delayed(Duration(seconds: 1), () => 100 + value))
      .then(print);//最后输出累加结果就是400
}

输出结果: image.png

  • 2. Future.catchError方法

注册一个回调,来处理有异常的Future

void main() {
  Future.delayed(Duration(seconds: 1), () => throw Exception('this is custom error'))
      .catchError(print);//catchError回调返回异常的Future
}

输出结果: image.png

  • 3. Future.whenComplete方法

Future.whenComplete方法有点类似异常捕获中的try-catch-finally中的finally, 一个Future不管是正常回调了结果还是抛出了异常最终都会回调 whenComplete 方法。

//with value
void main() {
  Future.value(100)
      .then((value) => print(value))
      .whenComplete(() => print('future is completed!'));
  print('main is executed');
}

//with error
void main() {
  Future.delayed(
          Duration(seconds: 1), () => throw Exception('this is custom error'))
      .catchError(print)
      .whenComplete(() => print('future is completed!'));
  print('main is executed');
}

输出结果: image.png image.png

6. Future使用的场景

由上述有关Future的介绍,我相信大家对于Future使用场景应该心中有个底,这里就简单总结一下:

  • 1、对于Dart中一般实现异步的场景都可以使用Future,特别是处理多个Future的问题,包括前后依赖关系的Future之间处理,以及聚合多个Future的处理。
  • 2、对于Dart中一般实现异步的场景单个Future的处理,可以使用async和await
  • 3、对于Dart中比较耗时的任务,不建议使用Future这时候还是使用isolate.

7. 熊喵先生的小总结

到这里有关异步编程中Future就介绍完毕了,Future相比isolate更加轻量级,很容易轻松地实现异步。而且相比aysnc,await方法在链式调用方面更具有优势。但是需要注意的是如果遇到耗时任务比较重的,还是建议使用isolate,因为毕竟Future还是跑在主线程中的。还有需要注意一点需要深刻理解EventLoop的概念,比如Event Queue, MicroTask Queue的优先级。因为Dart后面很多高级异步API都是建立事件循环基础上的。

感谢关注,熊喵先生愿和你在技术路上一起成长!