Dart是一个在单线程中运行的程序,如果遇到一个耗时的操作,程序将会被冻结。为了避免这种情况,可以使用一个异步操作使程序在等待耗时操作时完成时继续处理其他操作。在Dart中,可以使用Future对象来表示异步操作的结果。

1、Dart的消息循环机制

Flutter中的线程与异步UI - 图1

Dart中有一个事件循环(Event Loop) 来执行我们的代码,里面存在一个事件队列(Event Queue),事件循环不断从事件队列中取出事件执行。

1.1、微任务队列

  • 微任务队列的优先级要高于事件队列
  • 事件循环优先执行微任务队列中的任务,再执行事件队列中的任务
  • 所有的外部事件任务都在事件队列中,如:IO、计时器、点击、绘制事件
  • 微任务通常来源于Dart内部,并且微任务非常少

Dart的单线程中,代码执行顺序是怎样的呢?
1、Dart的入口函数是main函数,所以main函数中的代码会优先执行
2、main函数执行完成后,会启动一个事件循环(Event Loop),启动后开始执行队列中的任务
3、首先,会按照先进先出的顺序,执行 微任务队列(Microtask Quene) 中的所有任务
4、其次,会按照先进先出的顺序,执行 事件队列(Event Quene) 中的所有任务

1.2、创建微任务

在开发中,如果我们有一个任务不希望它放在Event Quene中排队,那么就可以创建一个微任务
我们可以通过dartasync下的scheduleMicrotask来创建一个任务

  1. import "dart:async";
  2. main(List<String> args) {
  3. scheduleMicrotask(() {
  4. print("Hello Microtask");
  5. });
  6. }

1.2.1、Future 的代码是加入微任务队列还是事件队列?

Future中通常有两个函数执行体:

  • Future构造函数传入的函数体
  • then函数的函数体

那么它们是加入到什么队列中呢?

  • Future的构造函数传入的函数体放事件队列中
  • then的函数体分以下几种情况:

1、Future没有执行完成,then函数会被添加到Futue函数执行体后
2、Future执行完成后执行thenthen函数体被放到微任务队列,当前Future执行完成后再执行微任务队列
3、如果Future是链式调用,则当前then函数体未执行完成,下一个then不会执行

2、async/await

Flutter是一个单线程并且跑着一个event loop,因此不必担心线程管理。如果你正在做I/O操作,可以安全地使用async/await来完成。如果需要让CPU执行繁忙的计算密集型任务,就需要使用Isolate来避免阻塞event loop

对于I/O操作,通过关键字async把方法声明为异步方法,然后通过await关键字等待该异步方法执行完成:

  1. loadData() async {
  2. String url = "https://xxxx";
  3. http.Response response = await http.get(url);
  4. setState(() {
  5. widgets = json.decode(response.body);
  6. });
  7. }

3、Isolate

Isolate是分离的运行线程,并且不和主线程的内存堆共享内存。这意味着你不能访问主线程中的变量,或者使用setState()来更新UIIsolate不能共享内存,每个lsolate都有自己的Event LoopQuene

下面是简单的Isolate的简单用法,把数据返回给主线程更新UI:

  1. import 'dart:isolate';
  2. ...
  3. loadData() async {
  4. //打开ReceivePort以接收传入的消息
  5. ReceivePort receiverPort = ReceivePort();
  6. //创建并生成与当前Isolate共享相同代码的Isolate
  7. await Isolate.spawn(dataLoader,receiverPort.sendPort);
  8. //流的第一个元素
  9. SendPort sendPort = await receiverPort.first;
  10. //流的第一个元素被收到后监听会关闭,所以需要新打开一个ReceivePort以接收传入的消息
  11. ReceivePort response = ReceivePort();
  12. //通过此发送端口向其对应的‘ReceivePort’发送异步[消息],这个消息是指要发送的参数
  13. sendPort.send(["https://xxxx", response.sendPort]);
  14. //获取端口发来的数据
  15. List msg = await response.first;
  16. //更新UI
  17. setState(() {
  18. widgets = msg;
  19. });
  20. }
  21. //Isolate的入口函数,该函数会在新的Isolate中调用, Isolate.spawn的message参数会作为调用它的唯一参数
  22. static dataLoader(SendPort sendPort) async {
  23. //打开ReceivePort以接收传入的消息
  24. ReceivePort port = ReceivePort();
  25. //通知其他的isolates,本isolate所监听的端口
  26. sendPort.send(port.sendPort);
  27. //获取其他端口发送的异步消息msg
  28. await for (var msg in port) {
  29. //等价于List msg = await port.first
  30. String data = msg[0];
  31. SendPort replyTo = msg[1];
  32. String dataURL = data;
  33. http.Response response = await http.get(dataURL);
  34. //其对应的“ReceivePort”发出解析出来的Json数据
  35. replyTo.send(json.decode(response.body));
  36. }
  37. }

dataLoader()是一个运行于自己独立线程上的Isolate。在Isolate里,你可以执行CPU密集型任务(如解析庞大的json,解析json也是很耗时的),或是计算密集型的数学操作,如加解密或信号处理等。