Dart 的同步与异步
我们都知道 Dart 是一个单线程模型的语言,但这并不意味着它只支持单线程,它也是支持多线程的,可以使用 isolate 进行实现的。所以,这个单线程的说法具有迷惑性,实际情况下,其单线程的执行只是形容它的事件循环机制,至于 IO、网络等操作,还是得放到 isolate 中。而 main 方法的执行,其实也是在一个 isolate 中。 所以,若是仅仅是说事件循环机制,使用 Java 也是同样可以模拟一个出来,只是 Android 提供了 Handler 机制去解决事件分发处理的问题,不需要我们自己重新封装。
事件循环机制
简单来说,代码的执行是顺序执行,从 main()
开始,等遇到 Microtask(微任务) 和 Timer(事件) 后,就分别用队列将其存储起来,当 main 方法执行完后,就会先去执行 Microtask 的消息队列,再去执行 Timer 的消息队列。
大致的流程如下所示:
下面用这个简单的例子验证下:
void main(List<String> arguments) async{
print('main start!');
//微任务
scheduleMicrotask((){
print('Microtask 执行!第一次');
});
//事件任务
Timer.run(() {
print('Timer 执行!第一次');
Timer.run(() {
print('Timer 执行!第二次');
});
scheduleMicrotask((){
print('Microtask 执行!第二次');
});
});
print('main end!');
}
输出结果:
main start!
main end!
Microtask 执行!第一次
Timer 执行!第一次
Microtask 执行!第二次
Timer 执行!第二次
Process finished with exit code 0
有两点要注意:
- 当在 Timer 中执行
scheduleMicrotask
和Timer.run
的时候,会往相应的队列添加数据,直到Microtask Queue
和Event Queue
都为空时,才会退出程序执行。 - 正常情况下,我们使用
Timer.run
即可,尽量不要使用scheduleMicrotask
,因为 Microtask 在 flutter 中会承载触摸事件等优先级较高的事件处理。
Timer 具体的存储位置在:sdk > lib > _internal > vm > lib > timer_impl.dart > _Timer_impl
中:
class _TimerHeap {
List<_Timer> _list;
int _used = 0;
_TimerHeap([int initSize = 7])
: _list = List<_Timer>.filled(initSize, _Timer._sentinelTimer);
······
}
Future
我们先来看个栗子🌰:
void main() {
print('main start');
Future.delayed(Duration(seconds: 3), (){
print('Future run');
});
print('main end');
}
main start
main end
Future run
Process finished with exit code 0
我们可以看到 Future 相比于 main()
确实延期执行了,就像开启了一个线程一样,那我们该如何去理解 Future?
这,我们可以从源码方面进行分析:
factory Future.delayed(Duration duration, [FutureOr<T> computation()?]) {
if (computation == null && !typeAcceptsNull<T>()) {
throw ArgumentError.value(
null, "computation", "The type parameter is not nullable");
}
_Future<T> result = new _Future<T>();
new Timer(duration, () {
if (computation == null) {
result._complete(null as T);
} else {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
}
});
return result;
}
很简单,其实它只是包装成了 Timer,所以,并没有开启一个新线程,而是按照了事件循环机制,放到 Event Queue
中,优先执行了 main()
。
那我们又该如何去理解 await 和 async ?
同样,我们再看看一个栗子🌰:
void main() async{
print('main start');
final data = await getNetworkData();
print('main end. network data: $data');
}
Future<String> getNetworkData(){
return Future.delayed(Duration(seconds: 3), () => 'I am robot');
}
main start
main end. network data: I am robot
Process finished with exit code 0
我们看日志输出效果,感觉 await 把 main()
阻塞了,只有成功获取得到 Future 的值才会继续执行下去。
但是,其实这里并没有进行阻塞,而是使用了 select 的概念,也就是非阻塞式等待。
这里就可能有人有疑问了?这阻塞式和非阻塞式有什么区别?
我们都知道,系统的 CPU 时间片的分配最小单位为线程,而程序的功能只是线程的代码执行而已,阻塞式就是当前线程放弃当前时间片,进入等待状态,交由其它线程执行完,再进行唤醒,再等待时间片进行执行;而非阻塞式则是没有进入等待状态,而是通过自旋的方式进行执行,等待其它任务完成,它再继续执行下去。
自旋的最简单理解就是:
var status = true;
while(status){}
等待更改 status 值从而跳出当前循环。
isolate
isolate 简单可以理解为一个线程,和 Java 的 Thread 相似,但是他们之间却还有很大的一个区别,我们先来看看 Java 的运行数据区:
这里有一个很明显的特点,就是线程具有共享的区域,特别是堆,说明线程之间的共享只要传堆的引用即可,无需真正拷贝数据过去,但是,这样就会出现一个问题,那便是共享的数据可能出现不安全的情况,即多线程能够同时修改同一份数据,由此,Java 延伸出锁的概念,Synchronized、ReentrantLock 等等便孕育而生,就是为了解决多线程并发问题。
Dart 为了避免这种情况,使用了另外一种概念:
也就是 isolate 之间尽可能不保持联系,他们之间的数据传输都是通过 port 传输真实的数据,而不是传对象的引用。
同时由于 isolate 的相对独立性,所以 Dart 不需要锁的概念,并且在内存回收上,无需 STW(Stop the world),直接回收 isolate 中全部资源即可。同样的,我们也来看看一个栗子🌰:
void main() async{
print('main start');
ReceivePort receivePort = ReceivePort();
Isolate.spawn(getNetworkData,["getUserInfo",receivePort.sendPort]);
//监听接收消息
receivePort.listen((message) {
print("收到消息:$message");
});
print('main end');
}
void getNetworkData(var message){
// 获取传入的数据
String path = message[0];
SendPort sendPort = message[1];
sendPort.send('path : $path, info : UserInfo');
}
输出的结果为:
main start
main end
收到消息:path : getUserInfo, info : UserInfo
我们可以看出,isolate 是通过 ReceivePort 和 SendPort 来进行数据的发送和接收。
另外,有一点需要注意,就是该程序运行后,并没有退出,ReceivePort 仍在等待消息,所以,我们需要适时将其关闭:
receivePort.listen((message) {
print("收到消息:$message");
receivePort.close();
});
转载自:https://juejin.cn/post/7256975111563198521