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 来帮忙

相关推荐
慧一居士17 分钟前
flex 布局完整功能介绍和示例演示
前端
DoraBigHead19 分钟前
小哆啦解题记——两数失踪事件
前端·算法·面试
一斤代码6 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子6 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年6 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子6 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina6 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路7 小时前
React--Fiber 架构
前端·react.js·架构
伍哥的传说8 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js
qq_424409198 小时前
uniapp的app项目,某个页面长时间无操作,返回首页
前端·vue.js·uni-app