咱们都知道 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 履行!第2次');
});
scheduleMicrotask((){
print('Microtask 履行!第2次');
});
});
print('main end!');
}
输出成果:
main start!
main end!
Microtask 履行!第一次
Timer 履行!第一次
Microtask 履行!第2次
Timer 履行!第2次
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();
});