字节笔记本

2026年2月22日

Flutter - Dart事件循环机制与异步

与 JavaScript 一样,Dart 是基于事件循环机制单线程模型,所以 Dart 中没有多线程,也就没有主线程与子线程之分。本文将深入介绍 Dart 的异步编程机制,包括事件循环、Future、async/await 以及 isolate 等核心概念。

一、Dart 异步

1、同步与异步

  • 同步:同一线程中,按照代码的编写顺序,自上而下依次执行(直观感受:需要等待)
  • 异步:代码执行中,某段代码的执行不会影响后面代码的执行(直观感受:无需等待)

2、单线程模型

单线程模型

  • 一条执行线上,同时且只能执行一个任务(事件),其他任务都必须在后面排队等待被执行
  • 为了不阻碍代码的执行,每遇到的耗时任务都会被挂起放入任务队列,待执行结束后再按放入顺序依次执行队列上的任务,从而达到异步效果

单线程模型与多线程各自的优势

  • 单线程模型的优势:避免了多线程的缺点,比较适合于需要等待对方传送数据或返回结果的耗时操作,如网络请求、IO 操作等
  • 多线程的优势:尽可能利用处理器的多核实现并行计算的计算密集型操作

多线程的缺点

  • 会带来额外的资源和性能消耗
  • 多个线程操作共享内存时需要加锁控制,锁竞争会降低性能和效率,复杂情况下还容易造成死锁

3、事件循环机制

对于用户点击、滑动、硬盘 IO 访问等事件,你不知道何时发生或以什么顺序发生,所以得有一个永不停歇且不能阻塞的循环来等待处理这些"突发"事件。于是,基于事件循环机制单线程模型就出现了。

Dart 事件循环机制由一个**消息循环(Event Looper)和两个消息队列(Event Queue)**构成,这两个消息队列分别是:事件队列(Event Queue)微任务队列(MicroTask Queue)

Event Looper

Dart 在执行完 main 函数后,Event Looper 就开始工作,Event Looper 优先全部执行完 Microtask Queue 中的 event,直到 Microtask Queue 为空时,才会执行 Event Looper 中的 event,Event Looper 为空时才可以退出循环。

注意:Event Looper 为空时,是可以而不是一定要退出,视场景而定。

Event Queue

Event Queue 的 event 来源于外部事件Future

  • 外部事件:例如输入/输出、手势、绘制、计时器、Stream 等
  • Future:用于自定义 Event Queue 事件

对于外部事件,一旦没有任何 microtask 要执行,Event loop 才会考虑 event queue 中的第一项,并且将会执行它。

通过 Future 实例向 Event Queue 添加事件:

dart
Future(() {
  // 事件任务
});

Microtask Queue

  • Microtask Queue 的优先级高于 Event Queue
  • 使用场景:想要在稍后完成一些任务(microtask)但又希望在执行下一个事件(event)之前执行

Microtask 一般用于非常短的内部异步动作,并且任务量非常少。如果微任务非常多,就会造成 Event Queue 排不上队,会阻塞 Event Queue 的执行(如:用户点击没有反应)。所以,大多数情况下优先考虑使用 Event Queue,整个 Flutter 源代码仅引用 scheduleMicroTask() 方法 7 次。

通过 scheduleMicrotask() 函数向 Microtask Queue 添加任务:

dart
scheduleMicrotask(() {
  // 微任务
});

二、Future

Dart 中的异步操作主要使用 Futureasync/await,整体与前端 ES6 中的 Promiseasync/await 的使用差不多,可以把 Future 理解为是一个自带 callback 效果的类。

1、基本使用

通过查看 Future 的构造函数知道,创建时需要传入一个返回值类型是 FutureOr 的函数:

dart
factory Future(FutureOr<T> computation()) {
  ...
}

这个 FutureOr 是一个联合类型,最终类型可能是 Future 或是泛型 T 的具体类型。当不指定泛型 T 时,实例类型为 Future。下面是一个模拟网络耗时请求的例子:

dart
Future<String> getNetworkData() {
  // 1. 将耗时操作包裹到Future的回调函数中
  return Future<String>(() {
    sleep(Duration(seconds: 2));
    return "Hello lqr"; // 只要有返回结果,那么就执行Future对应的then的回调(相当于Promise-resolve)
    // throw Exception("error"); // 如果没有结果返回(有错误信息),需要在Future回调中抛出一个异常(相当于Promise-reject)
  });
}

Future 实例有 3 个常用方法:

  • then((value){...}):正常运行时执行
  • catchError((err){...}):出现错误时执行
  • whenComplete((){...}):不管成功与否都会执行

通过以上 3 个方法,即可获得 Future 实例的执行状况与结果:

dart
main(List<String> args) {
  print("main start");
  // 2. 拿到结果
  var future = getNetworkData();
  future
      .then((value) => print(value)) // Hello lqr
      .catchError((err) => print(err))
      .whenComplete(() => print("执行完成")); // 不管成功与否都会执行
  print("main end");
}

日志输出如下:

text
main start
main end
// 2秒后输出:
Hello lqr
执行完成

注意,以上 3 个方法是可以分开写的,但每次执行完一个方法时需要对 future 实例重新赋值(相当于包了一层),否则后续方法无效:

dart
var future = getNetworkData();

// 错误写法:
future.then((value) => print(value));
future.catchError((error) => print(error)); // 无效

// 正确写法:
future = future.then((value) {
  print(value);
  return value;
});
future.catchError((error) => print(error)); // 有效

2、链式调用

Future 可以在 then() 方法中返回另一个 Future 实例,从而达到链式调用的效果,这对那些有数据关联的网络请求很有用:

dart
main(List<String> args) {
  print("main start");
  // 链式调用,执行多个数据处理
  Future(() {
    return "第一次结果";
  }).then((value) {
    print(value);
    return "第二次结果";
  }).then((value) {
    print(value);
    return "第三次结果";
  }).then((value) {
    print(value);
  }).catchError((error) {
    print(error);
  });
  print("main end");
}

强调:Future 构造函数要求传入一个返回值类型是 FutureOr 的函数,但因为 FutureOr 是联合类型,所以,这里可以返回另一个 Future 实例,或者是一个具体类型数据,比如字符串。

3、其它 API

Future 除了默认构造器外,还提供了几个常用的命名构造器:

  • Future.value():创建一个返回具体数据的 Future 实例
  • Future.error():创建一个返回错误的 Future 实例
  • Future.delayed():创建一个延时执行的 Future 实例
dart
main(List<String> args) {
  print("main start");

  Future.value("Hello lqr").then((value) => print(value));

  Future.error("出错了").catchError((error) => print(error));

  Future.delayed(Duration(seconds: 3))
      .then((value) => "Hello lqr")
      .then((value) => print(value));

  Future.delayed(Duration(seconds: 2), () => "Hello lqr")
      .then((value) => print("welcome"))
      .then((value) => throw Exception("出错了"))
      .catchError((error) => print(error))
      .whenComplete(() => print("执行完成")); // 不管成功与否都会执行

  print("main end");
}

三、async/await

async/await 是 Dart 提供的可以用同步的代码格式实现异步的调用过程语法糖

1、基本使用

  • await 必须在 async 函数中使用
  • async 函数返回的结果必须是一个 Future
dart
Future getNetworkData() async {
  var userId = await getUserId();
  var userInfo = await getUserInfo(userId);
  return userInfo.username // 会自动包裹成Future
}

如果不使用 async/await,那么上面的代码则需要这么写:

dart
Future getNetworkData() {
  return getUserId().then((userId) {
    return getUserInfo(userId);
  }).then((userInfo) {
    return userInfo.username;
  });
}

相比之下,使用 async/await 写出来的代码在理解上会更加清晰。

四、isolate

所有的 Dart 代码都是在 isolate 中运行的,它就是机器上的一个小空间,具有自己的私有内存块和一个运行着 Event Looper 的单个线程。每个 isolate 都是相互隔离的,并不像线程那样可以共享内存。一般情况下,一个 Dart 应用只会在一个 isolate 中运行所有代码,但如果有特殊需要,可以开启多个。

注意:Dart 中没有线程的概念,只有 isolate

1、创建 isolate(Dart API)

Dart 默认提供了 Isolate.spawn(entryPoint, message) 用于开启 isolate,通过源码可以知道形参 message 其实是形参 entryPoint 对应的函数执行时需要的参数:

dart
external static Future<Isolate> spawn<T>(
  void entryPoint(T message),
  T message, {
  bool paused = false,
  bool errorsAreFatal = true,
  SendPort? onExit,
  SendPort? onError,
  @Since("2.3") String? debugName
});

使用 Isolate.spawn(entryPoint, message) 开启 isolate,并指定要执行的任务:

dart
import 'dart:isolate';

main(List<String> args) {
  print("main start");
  Isolate.spawn(calc, 100);
  print("main end");
}

void calc(int count) {
  var total = 0;
  for (var i = 0; i < count; i++) {
    total += i;
  }
  print(total);
}

2、isolate 通信(单向)

isolate 间可以一起工作的唯一方法是通过来回传递消息。一般情况下,子 isolate 会将运行结果通过管道以消息的形式发送到 主 isolate,并在 主 isolateEvent Looper 中处理该消息,这时就需要借助 ReceivePort 来处理消息的传递了:

  • 在启动 子 isolate 时,将 主 isolate 的发送管道(SendPort)作为参数传递给 子 isolate
  • 子 isolate 在执行完毕时,可以利用管道(SendPort)给 主 isolate 发送信息
dart
import 'dart:isolate';

main(List<String> args) async {
  print("main start");

  // 1. 创建管道
  var receivePort = ReceivePort();

  // 2. 创建isolate
  Isolate isolate = await Isolate.spawn(foo, receivePort.sendPort);

  // 3. 监听管道
  receivePort.listen((message) {
    print(message);
    // 不再使用时,关闭管道
    receivePort.close();
    // 不再使用时,将 isolate 杀死
    isolate.kill();
  });

  print("main end");
}

void foo(SendPort sendPort) {
  sendPort.send("Hello lqr");
}

以上只实现了 isolate 的单向通信,双向通信比较麻烦,有兴趣可以再查看一些其他资料。

3、创建 isolate(Flutter API)

Flutter 提供了更为方便的开启 isolate 的 API:compute() 函数。以下是示例代码:

dart
main(List<String> args) async {
  int result = await compute(powerNum, 5);
  print(result);
}

int powerNum(int num) {
  return num * num;
}

compute() 是 Flutter 的 API,不是 Dart 的 API,所以,上面的代码只能在 Flutter 项目中才能运行。

参考资料


原文作者:GitLqr 原文链接:https://fullstackaction.com/pages/0fcdc8/ 来源:FSA全栈行动

分享: