本文为稀土技术社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
张风捷特烈 – 出品

一、Future 中的监听与告诉

在日常开发中,咱们一般知道对 Future 目标经过 thenonError 设置回调办法进行监听。但很少有机会了解 Future 中的回调是何时触发的, Future 像一个黑箱一样,对它越是不了解,就越是畏惧。本文,将带咱们从 Future 的源码动身,见识一下 Future 内部的风貌。


1. 深入知道 Future.delayed

如下是 Future.delayed 结构办法的代码,能够看出 推迟异步使命 实质上是:经过 Timer 敞开一个推迟回调。Future.delayed 结构中的第二入参是一个可空的 回调函数 - computation ,该函数触发的机遇也很明显:如下 424 行, 在 duration 时长之后,会触发 Timer 结构中传入的回调,当 computation 非空就会触发。

Duration delay = const Duration(seconds: 2);
Future.delayed(delay,(){
  print("task1 done");
});

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

这儿阐明一个小细节:咱们知道 Future 是一个笼统类,并不能直接实例化目标,但能够经过 factory 结构,创建 子类目标 回来。这儿 Future.delayed 便是一个工厂结构,上图中 418 行会创建 _Future 目标 result ,在 430 行对 result 目标进行回来。


如下代码中 Future.delayed 办法没有第二参,阐明结构时 computation 为空,走的是 421 行,触发 result_complete 办法表明使命完结。这能够阐明一个问题: Future#_complete 办法是触发 Future#then 回调的关键。

Future delayedTask = Future.delayed(delay);
delayedTask.then((value){
  print("task2 done");
});

Future#_complete 办法中能够看出,其入参是 FutureOr<T> ,阐明能够传入 Future 目标或 T 泛型目标。假如非 Future 目标,表明使命完结,会触发 _propagateToListeners 办法告诉监听者们。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现


2. 探寻 Future 的回调监听

咱们知道,Future 目标的完结机遇,能够经过 then 办法中的回调进行监听。在运转时的实际类型是 _Future ,所以看一下它的 then 办法完结。如下所示,then 办法的榜首参为回调函数,这儿的函数名为 f

314 行会将 f 函数被注册到 currentZone 的回调中;假如 onError 非空,也会在 _registerErrorHandler 办法在,被注册到 currentZone 的回调在中。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

终究会经过 _addListener 办法添加 _FutureListener 监听者目标,如下所示,能够推断出 _FutureListener 是一个链表结构,而且 _Future 类中持有链表的首节点 _resultOrListeners 。总而言之, _Future.then 中的首要作业是 注册回调添加监听者

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现


如下是 _FutureListener.then 的结构代码,能够看出 _FutureListener 中有 callbackerrorCallback 两个函数目标,他们将在 then 结构中被赋值。结合 _Future#then 在创建 _FutureListener 的代码(323 行)可知,用户传入的回调 f 作为榜首参,也便是为 _FutureListenercallback 目标赋值。

也便是说,_FutureListener#callback 被调用的场合,便是 then 中成功成果的回调机遇。就相当于 把鱼钩投入水中 (设置回调),接下来探究一下 何时鱼会上钩(触发告诉) 。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

PS : 不知为何 Dart 中不允许对这块内容进行 断点调试日志打印 ,所以只能依据源码的功用、结合代码中的线索进行剖析。假如有什么认知不对的地方,希望咱们能够友善讨论。


3. 探寻 Future 的触发告诉

从程序运转的逻辑上来看, Future#_complete 是触发 Future#then 中回调的原因。在 557 行所示,_propagateToListeners 办法在字面的意思上是告诉监听者。下面来详细看一下:

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

551 行会先触发 _removeListeners 办法移除监听者,并回来 _FutureListener 目标。该目标将作为 _propagateToListeners 的第二入参,也便是需求被告诉的监听者们。如下所示,_removeListeners 办法中会将 _Future_resultOrListeners 成员置空,也便是移除监听者的体现。 别的,回来值是经过 _reverseListeners 办法回来的 _FutureListener 目标:

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

_reverseListeners 办法是一个十分经典的单链表反转操作:比方进入办法时,current 是链表的首节点 A,且这以后有 BC 节点;那么办法履行完后,链接结构便是 C 为首节点,这以后是 BA 节点。终究一次 while 循环时,484prev 被赋值为 current,所以终究回来的 prev 目标便是首节点 C

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

从这儿能够看出,_removeListeners 办法的作用是置空 _Future#_resultOrListeners ,并将监听者链表反序回来。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现


_propagateToListeners 是界说在 _Future 中的静态办法,从代码注释中能看出:它能够触发 listeners 的回调。其间有两个入参,其一是 _Future 目标,其二是 _FutureListener 目标。在该 _complete 中被调用时,榜首参入参是 this 目标,第二入参是上面反序回来的监听者链表 首节点

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现


_propagateToListeners 办法内界说了三个函数,handleWhenCompleteCallbackhandleValueCallbackhandleError 分别用于处理 完结正确成果反常

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

其间 then 中的成果回调对应的是 handleValueCallback

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

如下,在 handleValueCallback 中,listener 会触发 handleValue 办法。其间 sourceResult 便是d当时 _Future 目标的 _resultOrListeners 。如下 tag1 处,在 _complete 办法 _setValue 时会将成果赋值给 _resultOrListeners

---->[_propagateToListeners]----
final dynamic sourceResult = source._resultOrListeners;
---->[_complete]----
void _complete(FutureOr<T> value) {
  // 略...
  _setValue(value as dynamic); // tag1
  _propagateToListeners(this, listeners);
}
---->[_setValue]----
void _setValue(T value) {
  assert(!_isComplete); // But may have a completion pending.
  _state = _stateValue;
  _resultOrListeners = value;
}

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现


如下是 _FutureListener#handleValue 的代码处理,其间 _onValuecallback 成员函数 ,也便是用户在 _Future#then 中传入的榜首参。sourceResult_Future#_complete 中传入的成果。这两者将作为参数,被 _zone 目标经过 runUnary 履行。

---->[_FutureListener#_onValue]----
@pragma("vm:recognized", "other")
@pragma("vm:never-inline")
FutureOr<T> handleValue(S sourceResult) {
  return _zone.runUnary<FutureOr<T>, S>(_onValue, sourceResult);
}
---->[_FutureListener#_onValue]----
FutureOr<T> Function(S) get _onValue {
  assert(handlesValue);
  return unsafeCast<FutureOr<T> Function(S)>(callback);
}

4. 整理一下当时的 Zone 目标

_FutureListener 中获取的 _zone_Future 目标持有的 _zone 。由于 result 目标是 _Future 类型的,在 _FutureListener 结构时赋值。如下是 _Future#then 中的处理 :

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

_Future 默许结构中运用的是 Zone._current_zone 赋值。

---->[_FutureListener#_zone]----
_Zone get _zone => result._zone;
---->[_Future#_zone]----
_Future() : _zone = Zone._current;

Zone#_current 默许是一个 _RootZone 类型的常量 _rootZone

---->[Zone#_zone]----
static _Zone _current = _rootZone;
const _Zone _rootZone = const _RootZone();

_current_Zone 的一个静态私有成员,所以它是能够变化的,由于是私有,它不能再外界被更改。所以在本文件中能够搜索其被赋值的场合。如下,在 _enter_leave 办法中会对 _current 成员进行更改,表明进入和离开范畴。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

别的,一个场合是在处理未捕获反常时,可能对 _current 成员进行修改:

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现


ZonerunUnary 办法,有两个参数,依据注释能够知道,该办法会在该 zone 中,将 argument 作为入参触发 action 函数。

---->[Zone#runUnary]----
/// Executes the given [action] with [argument] in this zone.
///
/// As [run] except that [action] is called with one [argument] instead of
/// none.
R runUnary<R, T>(R action(T argument), T argument);

Zone 是一个笼统类,_Zone 完结 Zone 接口,自身也是笼统类。_RootZone 承继自 _Zone ,是完结类。所以它必定要完结 runUnary 的笼统办法。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

runUnary_RootZone 中的完结如下,假如 Zone#_current_rootZone 会直接触发 f 回调,并将 arg 作为参数,runUnary 的回来值即为入参函数的回来值。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

假如 Zone#_current_rootZone 时, 会触发 _rootRunUnary 。在其间也会触发 f 函数,且触发前后会分别履行 _enter_leave 。这表明 f 函数履行期间 Zone#_current 会保持是 _rootZone

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

到这儿 Future 目标的 then 监听的触发流程就十分明晰的。 Future#then 中设置回调函数,种下一个 Future#_complete 中发送告诉,给出一个 ,触发回调。略微杂乱一点的是其间 _FutureListener 的链表结构,以及经过 Zone 目标履行回调函数。关于 Zone 目标的常识,是比较杂乱的,这儿先简略了解一下,在 FutureZone 的首要用途在代码上来看,是经过 runUnary 触发回调。


二、探究使命完结器 Completer

从上面能够看出 Future 自身仅仅封装了一套 监听 - 告诉 的机制。比方 then 参数监听的事情,经过 _complete 能够触发告诉,触发监听的回调。但何时触发 _complete 还是要 受制于人 的,比方推迟的异步使命,需求 Timer 目标的延时回调来触发 _complete

由于 _complete 是私有办法,这就导致咱们无法操作 Future 的完结状态。当需求灵敏操控 Future 目标完结状态的场景时,咱们就需求 Completer 类的帮助,但这个场景是比较少见的。


1. 知道 Completer 类

Completer 类自身十分简略,中心成员是 Future 类型的 future 成员。并有两个笼统办法 complete 用于完结使命,completeError 用于过错完结使命。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

也便是说 Completer 实质上仅仅对 Future 目标的一层封装,经过 Completer 供给的 API 来操作 future 成员而已。所以不用觉得 Completer 是什么高大上的东西。


Completer 自身是笼统类,其经过了工厂结构办法,回来的是 _AsyncCompleter 完结类。也便是说,假如直接经过 Completer() 创建目标,其运转时类型为 _AsyncCompleter

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

Future<int> foo(){
  Completer<int> _completer = Completer();
  return _completer.future;
}

_AsyncCompleter 集成自 _Completer ,其间只完结了 complete_completeError 两个办法。 complete 办法在触发 future 目标的 _asyncComplete 办法,终究也会触发 _completeWithValue 办法向监听者发送告诉,触发回调。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

所以 future 成员的实例化一定是在 _Completer 类中完结的。如下,_Completer 承继自 Completer,其间 future 成员是经过 _Future 直接结构的。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现


2. Completer 类的作用

总的来看, Completer 的唯一价值是能够让运用者操控 future 目标完结的机遇。而这个功用在绝大多数的场景中都是不需求的,由于关于异步使命而言,咱们等待使命完结的机遇,发送使命的机体是被迫的。

如下 delay3s 能够完结推迟 3s 的异步使命,可是这和 Future.delayed 在实质上并没有任何差异,只会把简略的事情搞杂乱。所以,没有操控 future 目标完结的机遇场合,都不需求运用 Completer 来自找麻烦。

Future<int> delay3s(){
  Completer<int> completer = Completer();
  Timer(const Duration(seconds:3 ),(){
    completer.complete(49);
  });
  return completer.future;
}

下面看一下源码中对 Completer 的一处运用场景体会一下。在 RefreshIndicator 组件的完结中,对应的状态类 RefreshIndicatorState 运用了 Completer 。如下所示,其间界说的 _pendingRefreshFuture 目标,是由 Completer 创建的。

---->[RefreshIndicatorState]---
late Future<void> _pendingRefreshFuture;
---->[RefreshIndicatorState#_show]---
final Completer<void> completer = Completer<void>();
_pendingRefreshFuture = completer.future;

如下所示,refreshResult 是运用者传入的 Future 目标,在其完结之后,需求 mounted 且形式是 refreash 时,才会调用 completer.complete() 。像这种需求在某些特点场合下,需求操控使命完结的机遇,是 Completer 的用武之地。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

_pendingRefreshFuture 将作为 show 办法的回来值,这样 show 办法这个异步使命的完结机遇,即 completer.complete() 触发的机遇。这相当于在 RefreshIndicatorState 中,创建了一个 Future 目标,并手动在恰当的机遇宣布完结。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现


三、 Future 中的四个静态办法

在第二篇初识 Future 时,咱们知道 Future 中有几个静态办法,那时候没有介绍,在这儿阐明一下。静态办法经过类名进行调用,是完结特定使命的工具办法,在运用上是比较方便的。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现


1. Future#await 办法

从办法界说上来看,Future#await 办法需求传入 T 泛型 Future 目标的列表,回来值泛型为是 T 型成果数据列表的 Future 目标。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

也便是说,Future#await 能够一起分发多个异步使命。如下所示,delayedNumberdelayedString 是两个推迟异步使命。经过 Future.wait 一起分发四个使命,从打印成果上能够看出,总耗时是 3s ,成果的顺序是 Future 列表中使命的顺序。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

void main() async {
  int start = DateTime.now().millisecondsSinceEpoch;
  List<dynamic> value = await Future.wait<dynamic>([
    delayedNumber(1),
    delayedString("a"),
    delayedNumber(1),
    delayedString('b'),
  ]);
  int cost = DateTime.now().millisecondsSinceEpoch-start;
  print("cost:${cost/1000} s, value:$value"); // [2, result]
}
Future<int> delayedNumber(int num) async {
  await Future.delayed(const Duration(seconds: 3));
  return 2;
}
Future<String> delayedString(String value) async {
  await Future.delayed(const Duration(seconds: 2));
  return value;
}

这个静态办法适合在需求多个不相关的异步使命 一起分发 的场合,不然要写对四个 Future 进行监听,代码处理时就会十分杂乱。终究的总耗时是这些使命中耗时最长的使命,不是所有使命的总和。


2. Future#any 办法

相同,Future#any 办法也需求传入 T 泛型 Future 目标的列表,但回来值是 T 泛型的 Future 目标。也便是说,该办法只允许有一个完结者,从体现上来看。它会回来榜首个 完结 的异步使命,不管成败。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

比方下面四个异步使命中, delayedString("a") 耗时 2s ,最快完结,然后被回来。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

void main() async {
  int start = DateTime.now().millisecondsSinceEpoch;
  dynamic value = await Future.any<dynamic>([
    delayedNumber(1),
    delayedString("a"),
    delayedNumber(3),
    delayedString('b'),
  ]);
  int cost = DateTime.now().millisecondsSinceEpoch-start;
  print("cost:${cost/1000} s, value:$value"); // [2, result]
}
Future<int> delayedNumber(int num) async {
  await Future.delayed(const Duration(seconds: 3));
  return num;
}
Future<String> delayedString(String value) async {
  await Future.delayed(const Duration(seconds: 2));
  return value;
}

多个使命中取最先完结的使命成果,其他使命作废,感觉 any 办法的运用场景不是很常见。了解一下即可,说不定什么时候就有同类竞争的使命需求呢。


3. Future#doWhile 办法

Future.doWhile 能够循环履行一个办法,直到该办法回来 false。如下所示,action 便是循环体,其间逻辑为 : 推迟 1svalue 自加,假如值为 3 回来 false

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

int value = 0;
void main()  {
  Future task = Future.doWhile(action);
  task.then((_){
    print('Finished with $value');
  });
}
FutureOr<bool> action() async{
  await Future.delayed(const Duration(seconds: 1));
  value++;
  if (value == 3) {
    return false;
  }
  return true;
}

Future.doWhile 办法的运用场景也比较特殊,当希望循环履行一些异步使命时,能够测验一下。有人可能觉得直接用 while 循环不比这简略易懂吗?由于 Future.doWhile 回来的是 Future 目标,咱们能够经过它进行监听使命完毕的履行情况,还是有些所势的。


4. Future#forEach 办法

Future#forEach 入参是 可迭代目标元素操作回调 ,很自然地能够想到它的作用是对可迭代目标进行 "异步加工" 。从源码完结来看,经过方才的 doWhile 办法对列表进行遍历,在 646 行运用 action 回调对元素进行处理。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

如下测试在,对 [0,1,2,3,4,5] 列表经过 forEach 进行处理,遍历期间触发 action ,每次推迟触发一秒。相同,感觉 Future.forEach 办法的运用场景也比较特殊,没什么太大的用处。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

void main()  {
  Future task = Future.forEach<int>([0,1,2,3,4,5], action);
  task.then((value){
    print('Finished $value');
  });
}
FutureOr<void> action(int element) async{
  await Future.delayed(const Duration(seconds: 1));
  int result = element*element;
  print(result);
}

四、 Future 与 微使命

关于 DartJavaScript 这种想在单线程中完结异步的言语,就脱离不了 事情循环机制 - Event Loop,本篇并不对这个概念进行打开。先介绍一下在事情循环中的两类事情。


1. 知道微使命 microtask

这儿再强调一下,Future 自身仅仅封装了一套 监听 - 告诉 的机制,并非异步触发的中心角色。 如下 Future 中供给了 microtask 结构,其间运用了 scheduleMicrotask 办法,传入一个回调,并且在回调在触发 _complete 进行完结告诉。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现


能够看出 scheduleMicrotaskTimer 是处于一个层级的,它们是触发异步使命的首要角色。别的在 Future 的默许结构在,运用 Timer.run 传入一个回调,并且在回调在触发 _complete 进行完结告诉:

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现

Timer.run 实质上便是一个 0s 定时器:

static void run(void Function() callback) {
  new Timer(Duration.zero, callback);
}

运用能够看出 Future 结构仅仅个 电视剧外壳,用于封装操作。其完结异步的中心是 TimerscheduleMicrotask 。到这儿,咱们应该抛除 Future 的外壳,经过下一层来一窥实质,所以就不再以 Future 为探究的焦点。


2. 微使命回调与 Timer 回调的异步性

scheduleMicrotask 是界说在 dart:async 包中的全局办法,其间能够传入一个回调函数。如下所示,2 没有阻塞 34 打印办法的履行,阐明入参的该回调函数是 异步触发 的。

void main(){
  print("done==1");
  scheduleMicrotask((){
    print("done==2");
  });
  print("done==3");
  print("done==4");
}
---->[日志]----
done==1
done==3
done==4
done==2

从作用上来看 Timer.runscheduleMicrotask 作用相似,都能够让传入的回调 异步触发

void main(){
  print("done==1");
  Timer.run((){
    print("done==2");
  });
  print("done==3");
  print("done==4");
}
---->[日志]----
done==1
done==3
done==4
done==2

3. Timer 和 scheduleMicrotask 异步使命的差异

这两种办法在触发的实质上还是有很大差异的,在下一篇探讨 事情循环机制 - Event Loop 时,会进行细致地剖析。现在,咱们先从 表象 上来看一下两者的差异:如下测试在是 7 个打印使命,其间 23 经过 Timer.run 异步触发;45 经过 scheduleMicrotask 异步触发。

void main(){
  print("done==1");
  Timer.run(()=> print("done==2"));
  Timer.run(()=> print("done==3"));
  scheduleMicrotask(()=> print("done==4"));
  scheduleMicrotask(()=> print("done==5"));
  print("done==6");
  print("done==7");
}

打印日志如下,能够看出虽然 scheduleMicrotask 设置回调的代码,在 Timer.run 之后,但优先级是 scheduleMicrotask 的处理较高。在同级的情况下,先加入的先履行,其实从这儿就能够看出一些 使命行列 的身影。

【Flutter 异步编程 -伍】 |  深入剖析 Future 类源码实现


scheduleMicrotask 办法注释中,有一个很有意思的小例子,能够很形象法地体现出 scheduleMicrotask 优先级高于 Timer.run 。如下所示,先在 Timer.run 回调中打印 executed, 然后界说 foo 办法,在其间经过 scheduleMicrotask 履行 foo 。这样 微使命行列 就永久不会停下, Timer.run 虽然是 0 s 之后回调,但永无回调机遇。

main() {
  Timer.run(() { print("executed"); });  // Will never be executed.
  foo() {
    scheduleMicrotask(foo);  // Schedules [foo] in front of other events.
  }
  foo();
}

本文到这儿,对 Future 类的剖析现已十分全面了,其间最重要的一点是: Future 自身仅仅封装了一套 监听 - 告诉 的机制。在 Future 的表象之下,隐藏着 TimerscheduleMicrotask , 以及其下的整个 事情循环机制 - Event Loop。下一篇,将从 TimerscheduleMicrotask 入手,揭开其背面秘密,终究你会发现 万物同源,天下大同 。那本文就到这儿,谢谢观看 ~