Dart 中的并发之Isolate

isolate

Dart 通过 async-await、isolate 以及一些异步类型概念(例如 FutureStream)支持了并发代码编程。

在应用中,所有的 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) 中执行某些计算任务,以避免阻塞主线程。

  1. static Future<R> run<R>(FutureOr<R> computation(), {String? debugName}):这是一个静态方法,用于执行一个异步计算任务,并返回一个 Future,该 Future 将在计算完成时得到结果。它接受两个参数:computation,一个无参函数或闭包,用于执行异步计算;debugName,一个可选的字符串参数,用于设置计算任务的调试名称。

  2. var result = Completer<R>();:创建一个 Completer 对象,用于控制异步计算的结果。

  3. var resultPort = RawReceivePort();:创建一个原始接收端口,用于接收隔离体中的计算结果。

  4. resultPort.handler:定义了接收端口的消息处理程序,当接收到消息时,将触发此处理程序。

  5. Isolate.spawn:这是关键部分。它用于在一个新的隔离体中执行计算任务。参数包括:

    • _RemoteRunner._remoteExecute:隔离体中要执行的函数。通常,这是 _RemoteRunner 类的 _remoteExecute 静态方法,它用于执行计算任务。
    • _RemoteRunner<R>(computation, resultPort.sendPort):这是 _RemoteRunner 的构造函数,它接受计算任务和发送端口作为参数,用于在隔离体中执行计算并将结果发送回主线程。
    • onErroronExit:分别指定了在隔离体中发生错误和隔离体退出时的处理程序。在这里,它们都使用了 resultPort.sendPort,以便将错误信息发送回主线程。
    • errorsAreFatal: true:指定任何错误都应视为致命错误,即使它们是未捕获的异步错误也应如此。
  6. resultPort.handler 的处理程序:在接收到隔离体中的消息时,这个处理程序会根据消息的内容来决定如何完成 Completer 对象。它可以处理以下情况:

    • 如果消息为 null,表示隔离体结束而没有发送结果,将会通过 result.completeError 报告错误。
    • 如果消息包含两个元素,分别是远程错误和堆栈信息,将会通过 result.completeError 报告错误。
    • 如果消息包含一个元素,将会通过 result.complete 报告结果。
  7. 异常处理:代码中也包含了异常处理部分,以处理可能发生的同步和异步异常。

总的来说,这段代码的目的是在隔离体中执行一个异步计算任务,并在计算完成后将结果或错误信息传递回主线程。这种方法可以保持主线程的响应性,即使在执行耗时计算时也不会阻塞用户界面。

占用内存

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 造成影响。

参考资料

Dart 中的并发

send_and_receive

long_running_isolate

计算耗时? Isolate 来帮忙

相关推荐
teeeeeeemo17 分钟前
JS数据类型检测方法总结
开发语言·前端·javascript·笔记
木木黄木木17 分钟前
HTML5 火焰字体效果教程
前端·html·html5
云墨-款哥的博客18 分钟前
失业学习-前端工程化-webpack基础
前端·学习·webpack
MAOX78921 分钟前
基于python的web系统界面登录
前端·python
懒大王、22 分钟前
Vue添加图片作为水印
前端·javascript·vue.js
Junerver26 分钟前
如何在Jetpack Compose中轻松的进行表单验证
前端·kotlin
3Katrina29 分钟前
《JavaScript this 指向深度剖析:从基础到复杂场景实战》
前端·javascript
岩柏32 分钟前
在vue项目中添加stylelint
前端
暖苏34 分钟前
Vue.js第一节
前端·javascript·css·vue.js·ecmascript
前端服务区38 分钟前
NodeJS文件流
前端