isolate
Dart 通过 async-await、isolate 以及一些异步类型概念(例如 Future
和 Stream
)支持了并发代码编程。
在应用中,所有的 Dart 代码都在 isolate 中运行。每一个 Dart 的 isolate 都有独立的运行线程,它们无法与其他 isolate 共享可变对象。在需要进行通信的场景里,isolate 会使用消息机制。很多 Dart 应用都只使用一个 isolate,也就是 main isolate。你可以创建额外的 isolate 以便在多个处理器核心上执行并行代码。
主isolate
通常一个 Dart 应用会在主 isolate 下执行所有代码。

就算是只有一个 isolate 的应用,只要通过使用 async-await 来处理异步操作,也完全可以流畅运行。一个拥有良好性能的应用,会在快速启动后尽快进入事件循环。这使得应用可以通过异步操作快速响应对应的事件。
Isolate.run()
如果应用受到耗时计算的影响而出现卡顿,例如 解析较大的 JSON 文件,可以考虑将耗时计算转移到单独工作的 isolate,通常我们称这样的 isolate 为 后台运行对象 。下图展示了一种常用场景,可以生成一个 isolate
,它将执行耗时计算的任务,并在结束后退出。这个 isolate 工作对象退出时会把结果返回。

每个 isolate 都可以通过消息通信传递一个对象,这个对象的所有内容都需要满足可传递的条件。并非所有的对象都满足传递条件,在无法满足条件时,消息发送会失败。举个例子,如果你想发送一个 List<Object>
,你需要确保这个列表中所有元素都是可被传递的。假设这个列表中有一个 Socket
,由于它无法被传递,所以你无法发送整个列表。
示例
单个函数调用中,isolate .run()
将isolate
实现的所有部分跟我们自己的解析文本任务结合。
在一个新的isolate
中读取和解析JSON数据, 然后存储返回的Dart表示。
dart
const filename = 'json_01.json';
Future<void> main() async {
final jsonData = await Isolate.run(() => _readAndParseJson(filename));
print('Received JSON with ${jsonData.length} keys');
}
没有端口,没有单独的生成、退出或错误处理,也没有特殊的返回结构。 直接读取filename
文件的内容, 解码JSON,并返回结果。
dart
Future<Map<String, dynamic>> _readAndParseJson(String filename) async {
final fileData = await File(filename).readAsString();
final jsonData = jsonDecode(fileData) as Map<String, dynamic>;
return jsonData;
}
isolate
以及在其之上编写的任何更高级别的 API 不再局限于仅运行静态或顶级函数。
耗时测试(展示isolate .run()速度)
scss
/// Isolate.run基准测试
Future<void> iosfib() async {
for (var i = 0; i < 2; i++) {
const int n = 38;
/// 计时器
var sw = Stopwatch()..start();
var fn = fib(n);
sw.stop();
print("fib($n) = $fn: ${sw.elapsed.toString()}");
/// 重置
sw.reset();
int compFun() => fib(n);
sw.start();
var fs = await Future.wait([
Isolate.run(() => compFun()),
Isolate.run(() => compFun()),
Isolate.run(() => compFun()),
]);
sw.stop();
/// 打印
print("fib($n) = ${fs[0]} * 3 : ${sw.elapsed.toString()}");
}
}
int fib(int n) => n <= 1 ? 1 : fib(n - 1) + fib(n - 2);
打印结果
yaml
flutter: fib(38) = 63245986: 0:00:00.309094
flutter: fib(38) = 63245986 * 3 : 0:00:00.631367
flutter: fib(38) = 63245986: 0:00:00.272038
flutter: fib(38) = 63245986 * 3 : 0:00:00.611722
从日志可以看出, 耗时计算的速度较正常计算提升不少。
Isolate.run()原理
dart
@Since("2.19")
static Future<R> run<R>(FutureOr<R> computation(), {String? debugName}) {
var result = Completer<R>();
var resultPort = RawReceivePort();
resultPort.handler = (response) {
resultPort.close();
if (response == null) {
// onExit handler message, isolate terminated without sending result.
result.completeError(
RemoteError("Computation ended without result", ""),
StackTrace.empty);
return;
}
var list = response as List<Object?>;
if (list.length == 2) {
var remoteError = list[0];
var remoteStack = list[1];
if (remoteStack is StackTrace) {
// Typed error.
result.completeError(remoteError!, remoteStack);
} else {
// onError handler message, uncaught async error.
// Both values are strings, so calling `toString` is efficient.
var error =
RemoteError(remoteError.toString(), remoteStack.toString());
result.completeError(error, error.stackTrace);
}
} else {
assert(list.length == 1);
result.complete(list[0] as R);
}
};
try {
Isolate.spawn(_RemoteRunner._remoteExecute,
_RemoteRunner<R>(computation, resultPort.sendPort),
onError: resultPort.sendPort,
onExit: resultPort.sendPort,
errorsAreFatal: true,
debugName: debugName)
.then<void>((_) {}, onError: (error, stack) {
// Sending the computation failed asynchronously.
// Do not expect a response, report the error asynchronously.
resultPort.close();
result.completeError(error, stack);
});
} on Object {
// Sending the computation failed synchronously.
// This is not expected to happen, but if it does,
// the synchronous error is respected and rethrown synchronously.
resultPort.close();
rethrow;
}
return result.future;
}
异步计算运行代码,通常用于在一个隔离体 (Isolate) 中执行某些计算任务,以避免阻塞主线程。
-
static Future<R> run<R>(FutureOr<R> computation(), {String? debugName})
:这是一个静态方法,用于执行一个异步计算任务,并返回一个Future
,该Future
将在计算完成时得到结果。它接受两个参数:computation
,一个无参函数或闭包,用于执行异步计算;debugName
,一个可选的字符串参数,用于设置计算任务的调试名称。 -
var result = Completer<R>();
:创建一个Completer
对象,用于控制异步计算的结果。 -
var resultPort = RawReceivePort();
:创建一个原始接收端口,用于接收隔离体中的计算结果。 -
resultPort.handler
:定义了接收端口的消息处理程序,当接收到消息时,将触发此处理程序。 -
Isolate.spawn
:这是关键部分。它用于在一个新的隔离体中执行计算任务。参数包括:_RemoteRunner._remoteExecute
:隔离体中要执行的函数。通常,这是_RemoteRunner
类的_remoteExecute
静态方法,它用于执行计算任务。_RemoteRunner<R>(computation, resultPort.sendPort)
:这是_RemoteRunner
的构造函数,它接受计算任务和发送端口作为参数,用于在隔离体中执行计算并将结果发送回主线程。onError
和onExit
:分别指定了在隔离体中发生错误和隔离体退出时的处理程序。在这里,它们都使用了resultPort.sendPort
,以便将错误信息发送回主线程。errorsAreFatal: true
:指定任何错误都应视为致命错误,即使它们是未捕获的异步错误也应如此。
-
resultPort.handler
的处理程序:在接收到隔离体中的消息时,这个处理程序会根据消息的内容来决定如何完成Completer
对象。它可以处理以下情况:- 如果消息为
null
,表示隔离体结束而没有发送结果,将会通过result.completeError
报告错误。 - 如果消息包含两个元素,分别是远程错误和堆栈信息,将会通过
result.completeError
报告错误。 - 如果消息包含一个元素,将会通过
result.complete
报告结果。
- 如果消息为
-
异常处理:代码中也包含了异常处理部分,以处理可能发生的同步和异步异常。
总的来说,这段代码的目的是在隔离体中执行一个异步计算任务,并在计算完成后将结果或错误信息传递回主线程。这种方法可以保持主线程的响应性,即使在执行耗时计算时也不会阻塞用户界面。
占用内存
css
One can expect the base memory overhead of an isolate to be in the order of 30 kb.
可以预期,隔离的基本内存开销约为30 kb。
总结
Isolate 工作对象可以进行 I/O 操作、设置定时器,以及其他各种行为。它会持有自己内存空间,与主 isolate 互相隔离。这个 isolate 在阻塞时也不会对其他 isolate 造成影响。