【Flutter 异步编程 -伍】 | 深入剖析 Future 类源码实现
一、Future 中的监听与通知
在日常开发中,我们一般知道对 Future 对象通过 then、onError 设置回调方法进行监听。但很少有机会了解 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");
});

这里说明一个小细节:我们知道 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 方法通知监听者们。

2. 探寻 Future 的回调监听
我们知道,Future 对象的完成时机,可以通过 then 方法中的回调进行监听。在运行时的实际类型是 _Future ,所以看一下它的 then 方法实现。如下所示,then 方法的第一参为回调函数,这里的函数名为 f 。
在 314 行会将 f 函数被注册到 currentZone 的回调中;如果 onError 非空,也会在 _registerErrorHandler 方法在,被注册到 currentZone 的回调在中。

最后会通过 _addListener 方法添加 _FutureListener 监听者对象,如下所示,可以推断出 _FutureListener 是一个链表结构,而且 _Future 类中持有链表的首节点 _resultOrListeners 。总而言之, _Future.then 中的主要工作是 注册回调 和 添加监听者 。

如下是 _FutureListener.then 的构造代码,可以看出 _FutureListener 中有 callback 和 errorCallback 两个函数对象,他们将在 then 构造中被赋值。结合 _Future#then 在创建 _FutureListener 的代码(323 行)可知,用户传入的回调 f 作为第一参,也就是为 _FutureListener 的 callback 对象赋值。
也就是说,_FutureListener#callback 被调用的场合,就是 then 中成功结果的回调时机。就相当于 把鱼钩投入水中 (设置回调),接下来探索一下 何时鱼会上钩(触发通知) 。

PS : 不知为何
Dart中不允许对这块内容进行断点调试和日志打印,所以只能根据源码的功能、结合代码中的线索进行分析。如果有什么认知不对的地方,希望大家可以友善讨论。
3. 探寻 Future 的触发通知
从程序运行的逻辑上来看, Future#_complete 是触发 Future#then 中回调的原因。在 557 行所示,_propagateToListeners 方法在字面的意思上是通知监听者。下面来详细看一下:
在 551 行会先触发 _removeListeners 方法移除监听者,并返回 _FutureListener 对象。该对象将作为 _propagateToListeners 的第二入参,也就是需要被通知的监听者们。如下所示,_removeListeners 方法中会将 _Future 的 _resultOrListeners 成员置空,也就是移除监听者的表现。 另外,返回值是通过 _reverseListeners 方法返回的 _FutureListener 对象:

_reverseListeners 方法是一个非常经典的单链表反转操作:比如进入方法时,current 是链表的首节点 A,且其后有 B、C 节点;那么方法执行完后,链接结构就是 C 为首节点,其后是 B 、A 节点。最后一次 while 循环时,484 行 prev 被赋值为 current,所以最终返回的 prev 对象就是首节点 C 。

从这里可以看出,_removeListeners 方法的作用是置空 _Future#_resultOrListeners ,并将监听者链表反序返回。

_propagateToListeners 是定义在 _Future 中的静态方法,从代码注释中能看出:它可以触发 listeners 的回调。其中有两个入参,其一是 _Future 对象,其二是 _FutureListener 对象。在该 _complete 中被调用时,第一参入参是 this 对象,第二入参是上面反序返回的监听者链表 首节点。

_propagateToListeners 方法内定义了三个函数,handleWhenCompleteCallback 、handleValueCallback 、handleError 分别用于处理 完成 、正确结果 和 异常 。

其中 then 中的结果回调对应的是 handleValueCallback :

如下,在 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;
}

如下是 _FutureListener#handleValue 的代码处理,其中 _onValue 是 callback 成员函数 ,也就是用户在 _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 中的处理 :

_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 成员进行更改,表示进入和离开领域。

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

Zone 的 runUnary 方法,有两个参数,根据注释可以知道,该方法会在该 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 的抽象方法。

runUnary 在 _RootZone 中的实现如下,如果 Zone#_current 是 _rootZone
会直接触发 f 回调,并将 arg 作为参数,runUnary 的返回值即为入参函数的返回值。

如果 Zone#_current 非 _rootZone 时, 会触发 _rootRunUnary 。在其中也会触发 f 函数,且触发前后会分别执行 _enter 和 _leave 。这表示 f 函数执行期间 Zone#_current 会保持是 _rootZone。

到这里 Future 对象的 then 监听的触发流程就非常清晰的。 Future#then 中设置回调函数,种下一个 因 。 Future#_complete 中发送通知,给出一个 果,触发回调。稍微复杂一点的是其中 _FutureListener 的链表结构,以及通过 Zone 对象执行回调函数。关于 Zone 对象的知识,是比较复杂的,这里先简单了解一下,在 Future 中 Zone 的主要用途在代码上来看,是通过 runUnary 触发回调。
二、探索任务完成器 Completer
从上面可以看出 Future 本身只是封装了一套 监听 - 通知 的机制。比如 then 参数监听的事件,通过 _complete 可以触发通知,触发监听的回调。但何时触发 _complete 还是要 受制于人 的,比如延迟的异步任务,需要 Timer 对象的延时回调来触发 _complete。
由于 _complete 是私有方法,这就导致我们无法操作 Future 的完成状态。当需要灵活控制 Future 对象完成状态的场景时,我们就需要 Completer 类的帮助,但这个场景是比较少见的。
1. 认识 Completer 类
Completer 类本身非常简单,核心成员是 Future 类型的 future 成员。并有两个抽象方法 complete 用于完成任务,completeError 用于错误完成任务。

也就是说 Completer 本质上只是对 Future 对象的一层封装,通过 Completer 提供的 API 来操作 future 成员而已。所以不用觉得 Completer 是什么高大上的东西。
Completer 本身是抽象类,其通过了工厂构造方法,返回的是 _AsyncCompleter 实现类。也就是说,如果直接通过 Completer() 创建对象,其运行时类型为 _AsyncCompleter 。

Future<int> foo(){
Completer<int> _completer = Completer();
return _completer.future;
}
_AsyncCompleter 集成自 _Completer ,其中只实现了 complete 和 _completeError 两个方法。 complete 方法在触发 future 对象的 _asyncComplete 方法,最后也会触发 _completeWithValue 方法向监听者发送通知,触发回调。

所以 future 成员的实例化一定是在 _Completer 类中实现的。如下,_Completer 继承自 Completer,其中 future 成员是通过 _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 的用武之地。

_pendingRefreshFuture 将作为 show 方法的返回值,这样 show 方法这个异步任务的完成时机,即 completer.complete() 触发的时机。这相当于在 RefreshIndicatorState 中,创建了一个 Future 对象,并手动在恰当的时机宣布完成。

三、 Future 中的四个静态方法
在第二篇初识 Future 时,我们知道 Future 中有几个静态方法,那时候没有介绍,在这里说明一下。静态方法通过类名进行调用,是完成特定任务的工具方法,在使用上是比较方便的。

1. Future#await 方法
从方法定义上来看,Future#await 方法需要传入 T 泛型 Future 对象的列表,返回值泛型为是 T 型结果数据列表的 Future 对象。

也就是说,Future#await 可以同时分发多个异步任务。如下所示,delayedNumber 和 delayedString 是两个延迟异步任务。通过 Future.wait 同时分发四个任务,从打印结果上可以看出,总耗时是 3s ,结果的顺序是 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 对象。也就是说,该方法只允许有一个完成者,从表现上来看。它会返回第一个 完成 的异步任务,无论成败。

比如下面四个异步任务中, delayedString("a") 耗时 2s ,最快完成,然后被返回。

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 就是循环体,其中逻辑为 : 延迟 1s 后 value 自加,如果值为 3 返回 false 。

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 回调对元素进行处理。

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

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 与 微任务
对于 Dart 、JavaScript 这种想在单线程中实现异步的语言,就脱离不了 事件循环机制 - Event Loop,本篇并不对这个概念进行展开。先介绍一下在事件循环中的两类事件。
1. 认识微任务 microtask
这里再强调一下,Future 本身只是封装了一套 监听 - 通知 的机制,并非异步触发的核心角色。 如下 Future 中提供了 microtask 构造,其中使用了 scheduleMicrotask 方法,传入一个回调,并且在回调在触发 _complete 进行完成通知。

可以看出 scheduleMicrotask 和 Timer 是处于一个层级的,它们是触发异步任务的主要角色。另外在 Future 的默认构造在,使用 Timer.run 传入一个回调,并且在回调在触发 _complete 进行完成通知:

而 Timer.run 本质上就是一个 0s 定时器:
static void run(void Function() callback) {
new Timer(Duration.zero, callback);
}
使用可以看出 Future 构造只是个 电视剧外壳,用于封装操作。其实现异步的核心是 Timer 和 scheduleMicrotask 。到这里,我们应该抛除 Future 的外壳,通过下一层来一窥本质,所以就不再以 Future 为探索的焦点。
2. 微任务回调与 Timer 回调的异步性
scheduleMicrotask 是定义在 dart:async 包中的全局方法,其中可以传入一个回调函数。如下所示,2 没有阻塞 3 、4 打印方法的执行,说明入参的该回调函数是 异步触发 的。
void main(){
print("done==1");
scheduleMicrotask((){
print("done==2");
});
print("done==3");
print("done==4");
}
---->[日志]----
done==1
done==3
done==4
done==2
从效果上来看 Timer.run 和 scheduleMicrotask 效果类似,都可以让传入的回调 异步触发 。
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 个打印任务,其中 2、3 通过 Timer.run 异步触发;4、5 通过 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 的处理较高。在同级的情况下,先加入的先执行,其实从这里就可以看出一些 任务队列 的身影。

在 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 的表象之下,隐藏着 Timer 和 scheduleMicrotask , 以及其下的整个 事件循环机制 - Event Loop。下一篇,将从 Timer 和 scheduleMicrotask 入手,揭开其背后秘密,最终你会发现 万物同源,天下大同 。那本文就到这里,谢谢观看 ~
转载自:https://juejin.cn/post/7152303127494918180