浅谈 Flutter 的并发和 isolates

2,218 阅读4分钟

有没有一种感觉,就我自己而言,Flutter 项目开发了好几个了,但是对这个 isolates 印象依旧很陌生,日常开发中好像很少见到它身影或者用到它,但真实情况是这样的吗?今天就来聊一聊它。

Flutter 中的 isolates 是什么?

Flutter 中,Isolates(隔离区)是一种轻量级的并发执行单元,用于在单个进程中执行代码。与线程有点儿像,但是每个 Isolates 之间又是完全独立的,彼此之间没有共享内存。每个 Isolates 都有自己的内存堆,可以独立执行代码,处理任务,而不会受到其他 Isolates 的影响。它们之间只能通过消息传递进行通信。

我们可以通过 Isolate.current 来查看当前正在执行的 Isolate

正在执行的 Isolate

Flutter 中有哪些 isolates

Flutter 中,主要有两种类型的 Isolates

  • Main Isolates: 也称为 UI 线程或 UI Isolate,启动应用程序时,Dart VM 会自动创建一个 Isolate 实例并在其上运行您的“主”代码,相当于是 Flutter 应用程序的主线程,负责处理用户界面和用户交互。在 Main Isolate 中执行的代码通常包括构建 UI、处理用户输入等任务。打印 Isolate.current.debugName 可以看到:

    Main Isolate

  • Background Isolates:运行在后台,可以用于执行耗时操作,如网络请求、长的JSON 解析、文件读取、计算密集型任务等。通过后台 Background Isolates,可以避免在主 Isolate 中执行长时间运行的任务,从而保持应用程序的响应性。

默认情况下,Flutter 应用程序会在 Main Isolates 上完成所有工作,切处理速度很快,不会出现 UI 卡顿,但执行异常大的计算的时候,会出现 UI 卡顿,和原生开发一样,在子线程执行耗时操作,在 Flutter 中就需要将耗时操作放在辅助的 Isolates 中,也就是 Background Isolates

事件循环与页面卡顿

每个 Isolates 都有自己的内存和事件循环,事件循环是按照事件添加到事件队列的顺序来处理事件。在 Main Isolates 中,这些事件可以是用户的点击事件、函数执行和绘制视图到屏幕上等等。 为了更平滑的渲染视图,Flutter 底层会以60次/每秒向事件队列添加绘制帧事件。 如果这些事件没有及时处理,就会出现 UI 卡顿或者没有响应。

事件循环与页面卡顿

如果某个事件无法在两帧之间的时间(帧间隙)内完成时,最好将这个事件的执行放在其它的 Isolates,用来确保 Main Isolates 每秒可以生成 60 帧。

Isolates 之间通信

创建一个新的 isolate 有两种方式,构造方法 Isolate(...) 和 静态方法 Isolate.spaw。下面例子创建一个新的 isolate 每隔一秒向 Main Isolates 发送当前时间戳。

Future<void> main() async {
  if (kDebugMode) {
    print("Isolate.current = ${Isolate.current.debugName}");
  }

  Isolate timerIsolate;
  final mainControlPort = ReceivePort()..listen((message) {
    print("sending back to MainIsolate: $message");
  });

  timerIsolate = await Isolate.spawn(
    timerTick,
    mainControlPort.sendPort,
    debugName: "TimerIsolate",
  );

  runApp(App());
}

void timerTick(SendPort mainPort) async {
  print("${Isolate.current.debugName} started");
  Timer.periodic(const Duration(seconds: 1), (timer) {
    final ts = DateTime.now().toIso8601String();
    mainPort.send(ts);
  });
}

执行结果的 log

Performing hot restart...
Syncing files to device iphone...
Restarted application in 886ms.
flutter: Isolate.current = main
flutter: TimerIsolate started
flutter: sending back to MainIsolate: 2024-03-29T16:49:39.598746
flutter: sending back to MainIsolate: 2024-03-29T16:49:40.594376
flutter: sending back to MainIsolate: 2024-03-29T16:49:41.594763
flutter: sending back to MainIsolate: 2024-03-29T16:49:42.594927
flutter: sending back to MainIsolate: 2024-03-29T16:49:43.593959
flutter: sending back to MainIsolate: 2024-03-29T16:49:44.594098
flutter: sending back to MainIsolate: 2024-03-29T16:49:45.594441
flutter: sending back to MainIsolate: 2024-03-29T16:49:46.594051
flutter: sending back to MainIsolate: 2024-03-29T16:49:47.594242
Application finished.

isolate消息传递的流程图如下:

Isolates 之间通信

实际上,FlutterIsolates 就是 Actor model 实现,Isolates 之间只能通过消息传递来相互通信,而且消息是通过复制的方式从发送 Isolate 传递到接收的 Isolate,也就是说,当这个数据消息在接收的 Isolate 上发生变化的时候,发送 Isolate 的原数据不受影响。

SendPort.send: 发送时生成可变消息的副本

传递的消息是不可变对象时 immutable objects,如不可变的字符串,会发送对该对象的引用,而不是复制的对象,这样做也是为了获得更好的性能。不可变对象无法更新,也符合Actor model 的实现。

Isolate.exit:发送对消息的引用

特殊情况就是当发送方Isolate 在发送完消息后就销毁了,会将消息的所有权传递给接收的 Isolate,以确保只有一个 Isolate 可以访问该消息。

根据这些特性,所以我们在使用 Isolate 要注意这一点,当你在 Isolates 传递一个全局可变变量时,该全局变量会在其它 Isolate 复制一份,而在主隔离中却保持不变。

还有一点,目前 Web 平台还不支持 Isolate

本篇文章就到这里,感谢您的阅读,也希望您能关注我的公众号 Flutter技术实践,原创不易,您的关注是我更新下去最大的动力。