随着Flutter的使用越来越广泛,相信很多人包括我自己对flutter的线程一直存一些疑问, dart为什么默认是单线程任务处理、在单线程下dart的异步是如何实现的、flutter线程有哪些、如何使用多线程处理耗时操作... 带着这些疑问去探索下flutter engine多线程、 dart isolate 和 异步 三者之前的关系。
首先介绍下Flutter engine 的多线程,从源码可以看到有五种线程,platform、UI、raster、io、profiler,profiler 线程 只有在profile Mode才会开启。
ThreadHost::ThreadHost(std::string name_prefix_arg, uint64_t mask)
: name_prefix(name_prefix_arg) {
if (mask & ThreadHost::Type::Platform) {
platform_thread = std::make_unique(name_prefix + ".platform");
}
if (mask & ThreadHost::Type::UI) {
ui_thread = std::make_unique(name_prefix + ".ui");
}
if (mask & ThreadHost::Type::RASTER) {
raster_thread = std::make_unique(name_prefix + ".raster");
}
if (mask & ThreadHost::Type::IO) {
io_thread = std::make_unique(name_prefix + ".io");
}
if (mask & ThreadHost::Type::Profiler) {
profiler_thread = std::make_unique(name_prefix + ".profiler");
}
}
官方对flutter engine 多线程做了详细介绍,做了如下摘要:
Flutter engine 不创建或管理自己的线程。而是由中间层embeder为Flutter引擎创建和管理线程(以及它们的消息循环)。embeder给Flutter engine 提供它所管理的线程的Task Runner。除了embeder为engine管理的线程外,Dart VM也有自己的线程池。无论是Flutter egnine还是embeder都无法访问该池中的线程。
PlartForm Task Runer对应的线程就是主线程,类似于 Android 的 MainThread 以及 iOS 的 UIKit 文档。
尽管阻塞平台线程的时间过长不会阻塞Flutter的渲染,但平台确实对这个线程上的耗时操作施加了限制。因此,官方建议在响应平台消息时,任何昂贵的工作都应该在单独的工作线程(与上面讨论的四个线程无关)上执行,然后再将响应排回到平台线程上,提交给引擎。不这样做可能会导致平台特定的看门狗终止应用程序。Android和iOS等嵌入系统也使用平台线程来处理用户输入事件。一个阻塞的平台线程也会导致手势被丢弃。
除了为engine构建最终渲染的框架外,root isolate 需要执行、计时器、microtaskQueue 和异步I/O 的所有响应
由于UI线程构建了决定引擎最终会在屏幕上绘制什么的图层树,所以屏幕上所有能看到的UI都来源于这里,不能阻塞该线程
根据处理图层树和设备完成显示帧所需的时间,Raster Task runner 的各个组件可能会延迟UI线程上的进一步帧的调度。通常情况下,UI和Raster Task runner 是在不同的线程上。在这种情况下,Raster线程可能正在向GPU提交一帧,而UI线程已经在准备下一帧。这种管道机制确保了UI线程不会为Raster Task runner 安排过多的任务。
这个线程之前被叫做「GPU 线程」,因为它为 GPU 进行栅格化,但我们重新将它命名为「raster 线程」,这是因为许多开发者错误的(但是能理解)认为该线程运行在 GPU 单元。
到目前为止,所有提到的Task Runner都对可以在这上面执行的各种操作有相当强的限制。阻断Platform Task Runner 的时间过长可能会触发平台的看门狗,而阻断UI或Raster Task Runner 都会导致Flutter应用程序的卡顿。 Raster有一些必要的任务,需要做一些非常耗时的工作。这种耗时的工作是在IO任务运行器上进行的,
比如 I/O线程 从存储中读取图片解码后将数据交给Raster线程处理最终交接给GPU,从而减轻了Raster线程的压力;
通过查看 Flutter engine 源码,开启profiler模式会启动profiler_thread 获取对应的 TaskRunner(),有点类似于开辟常驻线程,检测卡顿性能。官方推荐我们在profile mode下监测性能。
- (void)startProfiler {
FML_DCHECK(!_threadHost->name_prefix.empty());
_profiler_metrics = std::make_shared();
_profiler = std::make_shared(
_threadHost->name_prefix.c_str(), _threadHost->profiler_thread->GetTaskRunner(),
[self]() { return self->_profiler_metrics->GenerateSample(); }, kNumProfilerSamplesPerSec);
_profiler->Start();
}
Isolate class
An isolated Dart execution context.
All Dart code runs in an isolate, and code can access classes and values only from the same isolate. Different isolates can communicate by sending values through ports (see ReceivePort, SendPort).
官方isolate的定义,「 Isolate 是一个隔离的Dart执行上下文 」,dart代码都运行在isolate, 代码只能在同一个isolate获取类和值,不同的isolate只能通过发送port和接受port进行消息传递
不同于其他平台,例如 iOS多个线程可以对一个对象做读写操作,需要「添加锁」使其遵循「多读单写」的原则
Dart的线程是 isolate 形式存在的,不同线程的内存是互相隔离的,没有共享的内存,不会造成资源竞争,也不需要使用锁,更不会有死锁以及锁造成的性能消耗等问题
- 大多数的flutter App 运行在单独的isolate上,如果复杂的计算或者比较重的耗时操作(百毫秒以上)造成UI卡顿,我们也可以使用 Isolate.spawn() or Flutter’s compute() function. 创建新的isolate来执行异步耗时任务,实现任务并发
- 每个isolate 都有自己独立的内存空间、eventloop、eventQueue、MicrotaskQueue 如下图
- isolate 可以通过sendPort 以及 receivePort 以stream的方式 互相传递信息,receivePort继承stream
过程有点类似于socket 三次握手
import 'dart:isolate';
void main(List args) async {
print("current thread ${Isolate.current.debugName}");
ReceivePort r1 = ReceivePort();
SendPort s1 = r1.sendPort;
Isolate.spawn(childIsolate, s1);
SendPort s2 = await r1.first;
var data = await sendtoReceive(s2, "Hi 我是主线程");
print("主线程收到消息:$data");
}
Future sendtoReceive(SendPort port, msg) {
ReceivePort reponse = ReceivePort();
port.send([reponse.sendPort, msg]);
return reponse.first;
}
// 创建新的isolate
void childIsolate(SendPort s1) async {
print("current thread ${Isolate.current.debugName}");
ReceivePort r2 = ReceivePort();
SendPort s2 = r2.sendPort;
s1.send(s2);
var data = await r2.first;
SendPort replyPort = data[0];
print("子线程收到: ${data[1]}");
replyPort.send("Hello this is child isolate");
最终打印结果
current thread main
current thread childIsolate
子线程收到: Hi 我是主线程
Hello this is child isolate
isolate 还有pause、resume、kill 等api 可以动手尝试。
compute 方式实际上是对isolate 一系列方法的封装,类似于iOS dispatch_async 和 NSOperationQueue的关系。
await compute(function())
function() 为耗时方法, compute 对返回值的类型是有要求的。
当启动一个flutter程序后,做完一些初始化操作后,会创建上述四个taskRunner 以及对应的线程,创建DartVM 和engine,然后会创建Root Isolate运行在DartVM。
DartVM拥有自己线程池(isolate), Flutter engine 和 embeder 都不能直接访问. isolate的生命周期完全由dartVM管理。
上文提到UI线程为root isolate 执行所有的 Dart 代码, 原因在于 root isolate 会通过task_dispatch发送消息,将任务提交给UI Task Runner. 通过 isolate.spawn创建的isolate 没有这个权限。
Engine thread 和 Dart Isolate 通过互相发送消息来执行dart代码,Engine thread 由embeder 创建管理,Isolate由DartVM管理 两者是相互独立,彼此协作的关系。
Flutter是依赖于Dart语言的跨平台框架,使用dart语言就需要模拟dart运行环境,创建DartVM,DartVM中 Root isolate 有且仅有一个 所以 Dart默认是单线程任务处理,使用dart语言的Flutter的App 默认也是单线程任务处理,如果需要实现并发,需要新建isolate
谈到异步首先要介绍EventLoop和Future, 这是因为有了 EventLoop和Future 使 Dart 可以异步执行任务
Dart 一次只能执行一个任务,任务按照顺序一个接一个的执行,不能被其他任务打断。那 dart 是如何实现异步操作的呢?
Isolate 会维护 EventLoop, 执行完Main()中的任务后,EventLoop会循环执行两个队列的任务 MicrotaskQueue、eventQueue。
future 是 Future
当你写的代码依赖于 future 对象时,你有两种可选的实现方式:
async 和 await 被称作语法糖,下面的解释用于形容语法糖很恰当。
使用Future 可以把实现异步操作,但是哪些任务会被直接执行,哪些任务会放到eventQueue,哪些任务被放到MicrotakQueue,通过代码调试可以得出总结出。
void _futuretest() async {
Future.delayed(Duration(seconds: 1), () => print("Event Queue 3"));
Future(() => {print("Event Queue 1")}).then((value) => print("then 3"));
Future.delayed(Duration.zero, () => print("Event Queue 2")).then((value) {
scheduleMicrotask(() => print("Microtask 6"));
print("then 2");
}).then((value) {
print("then 1");
});
scheduleMicrotask(() => {print("Microtask 1")});
Future.microtask(() => print("Microtask 2"))
.then((value) => print("then 4"));
Future.value().then((value) => print("Microtask 3"));
print("main 1");
Future.sync(() => print("vsync 2")).then((value) => print("Microtask 4"));
Future.value(getValue()).then((value) => print("Microtask 5"));
print("main 4");
}
String getValue() {
print("value 3");
return "value 3";
}
// 打印结果:
flutter: main 1
flutter: vsync 2
flutter: value 3
flutter: main 4
flutter: Microtask 1
flutter: Microtask 2
flutter: then 4
flutter: Microtask 3
flutter: Microtask 4
flutter: Event Queue 1
flutter: then 3
flutter: Event Queue 2
flutter: then 2
flutter: then 1
flutter: Microtask 6
flutter: Event Queue 3
flutter: Event Queue 4
直接调用
Microtask
Eventtask
使用future 仅仅只是改变了任务的调用顺序,任务仍然是串行的,而新建isolate 才是真正实现了并发;Future 和 Isolate 类似于异步和并发的关系
实际开发中Future 能满足我们大多数的使用需求,毕竟isolate的创建和销毁也会耗时,只有一些耗时操作(百毫秒级别)建议使用compute,具体还要根据实际开发需求,选择合适的方案
本文主要探讨了Flutter engine 多线程机制 以及 dart isolate 实现原理。 相信经过上述分析,大家对flutter和dart线程有了更深入的理解,如有纰漏还望指出,希望大家多多支持。