0%

浅谈 Flutter 的并发和 isolates

有没有一种感觉,就我自己而言,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次/每秒 向事件队列添加“绘制帧”事件(对于 60Hz 设备)。 如果这些事件没有及时处理,就会出现 UI 卡顿或者没有响应。

事件循环与页面卡顿

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

Isolates 之间通信

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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技术实践,原创不易,您的关注是我更新下去最大的动力。

Flutter技术实践