likes
comments
collection
share

【Flutter基础】Dart中的并发Isolate

作者站长头像
站长
· 阅读数 21

前言

说到 Flutter 中的异步,我想大家都不陌生。一般我们使用 Futureasync-await 来进行网络请求、文件读取等异步加载,但要提到 Isolate ,大家就未必能够说的明白了,今天我就带大家了解下 Dart 中的并发 Isolate

一、Isolate的基本用法

1.1 Isolate的基本用法

对于 Isolate ,我们一般通过 Isolate.spawn() 来实现并发处理。

  const String downloadLink = '下载链接';
  final resultPort = ReceivePort();
  await Isolate.spawn(readAndParseJson, [resultPort.sendPort, downloadLink]);
  String fileContent = await resultPort.first as String;
  print('展示文件内容: $fileContent');

Isolate.spawn() 内部传递一个 entryPoint 初始化函数,用于执行异步操作。这里我们定义 readAndParseJson 函数,通过设置 延迟2秒 来模拟文件读取。

Future<void> readAndParseJson(List<dynamic> args) async {
  SendPort resultPort = args[0];
  String fileLink = args[1];

  print('获取下载链接: $fileLink');

  String fileContent = '文件内容';
  await Future.delayed(const Duration(seconds: 2));
  Isolate.exit(resultPort, fileContent);
}

运行结果:

【Flutter基础】Dart中的并发Isolate

1.2 Isolate的异常处理

由于 Isolate 开启的是一块新的隔离区,完全和启动的 Isolate 独立,自然是无法通过 try-catch 进行捕获。

好在 Isolate 提供了异常通知的能力,我们依旧可以通过 ReceivePort 来接收 Isolate 产生的异常,代码如下所示:

  const String downloadLink = '下载链接';
  final resultPort = ReceivePort();

  await Isolate.spawn(
    readAndParseJsonWithErrorHandle,
    [resultPort.sendPort, downloadLink],
    onError: resultPort.sendPort,
    onExit: resultPort.sendPort,
  );

  // 获取结果
  final response = await resultPort.first;
  if (response == null) { // 没有消息
    print('没有消息');
  } else if (response is List) { // 异常消息
    final errorAsString = response[0]; //异常
    final stackTraceAsString = response[1]; // 堆栈信息
    print('error: $errorAsString \nstackTrace: $stackTraceAsString');
  } else { // 正常消息
    print(response);
  }

readAndParseJsonWithErrorHandle 函数中,我们通过手动 throw Exception 来触发异常处理。

Future<void> readAndParseJsonWithErrorHandle(List<dynamic> args) async {
  SendPort resultPort = args[0];
  String fileLink = args[1];
  String newLink = '文件链接';

  await Future.delayed(const Duration(seconds: 2));
  throw Exception('下载失败');
  Isolate.exit(resultPort, newLink);
}

运行结果:

【Flutter基础】Dart中的并发Isolate

1.3 Isolate.run()

如果我们每次使用时都需要通过 ReceivePort 来实现 Isolate 的消息通信,这样会过于繁琐。好在官方也考虑到了这个问题,通过提供 Isolate.run() 来直接获取返回值:

注意:该方法需要在 Dart 2.19 以上的版本使用,对应 Flutter 3.7.0 以上。

 const String downloadLink = '下载链接';
 String fileContent = await Isolate.run(() => handleReadAndParseJson(downloadLink));
 print('展示文件内容: $fileContent');

/// 处理读取并解析文件内容
Future<String> handleReadAndParseJson(String fileLink) async {
  print('获取下载链接: $fileLink');
  String fileContent = '文件内容';
  await Future.delayed(const Duration(seconds: 2));
  return fileContent;
}

其原理就是内部通过对 Isolate.spawn() 进行封装,通过 Completer 来实现 Future 的异步回调。关键代码如下:

    var result = Completer<R>();
    var resultPort = RawReceivePort();
    ...
    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;
    }

Tip:从官方的源码中我们可以学到,在调用 Isolate.spawn() 时,建议通过 tray-catch 捕获可能发生的异常,并且在最后需要关闭 ReceivePort 避免内存泄漏。

二、Flutter中的compute

除了上述在 Dart 中的用法外,我们还可以在 Flutter 中通过 compute() 来实现。并且这也是官方推荐的用法,因为 compute() 允许在非原生平台 Web 上运行。

官方原文:If you’re using Flutter, consider using Flutter’s compute() function instead of Isolate.run(). The compute function allows your code to work on both native and non-native platforms. Use Isolate.run() when targeting native platforms only for a more ergonomic API.

2.1 compute的使用

Isolate.run() 的使用方式类似,通过传入 callback 函数让 Isolate 执行:

  String content = await compute((link) async {
    print('开始下载: $link');
    await Future.delayed(const Duration(seconds: 2));
    return '下载的内容';
  }, '下载链接');
  print('完成下载: $content');

运行结果:

【Flutter基础】Dart中的并发Isolate

Tip:在引入 Flutter 包之前,我们可以直接右键 run 'islate.dart' with Coverage 在Coverage 运行;在引入 Flutter 包之后,我们就需要在手机上运行,可以通过命令:open -a simulator 启动一个 iOS 模拟器运行。

2.2 compute的异常处理

查看 compute() 源码发现,内部使用的是 Isolate.run(),而 Isolate.run() 内部是通过 Completer 来完成异步回调的。因此,我们直接通过 try-catch 即可捕获异常:

  try {
    await compute((link) async {
      await Future.delayed(const Duration(seconds: 2));
      throw Exception('下载失败');
    }, '下载链接');
  } catch (e) {
    print('error: $e');
  }
  print('结束');

运行结果:

【Flutter基础】Dart中的并发Isolate

2.3 compute的源码分析

compute() 实际是对 isolates.compute 的实例化:

const ComputeImpl compute = isolates.compute;

Isolates 却是通过不同的平台来指定引入的类,这样也印证了为什么官方推荐在 Flutter 中使用 compute()

import '_isolates_io.dart'
  if (dart.library.js_util) '_isolates_web.dart' as isolates;

_isolates_io.dart 中是通过 Isolate.run() 来实现:

Future<R> compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, {String? debugLabel}) async {
  debugLabel ??= kReleaseMode ? 'compute' : callback.toString();

  return Isolate.run<R>(() {
    return callback(message);
  }, debugName: debugLabel);
}

_isolates_web.dart 中是通过 await null; 抽取单帧来执行函数:

Future<R> compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, { String? debugLabel }) async {
  // To avoid blocking the UI immediately for an expensive function call, we
  // pump a single frame to allow the framework to complete the current set
  // of work.
  await null;
  return callback(message);
}

2.4 compute小结

  1. 因为 compute() 需要引入 flutter/foundation.dart,所以只能在 Flutter 中运行。
  2. 在 Flutter 中推荐使用 compute() 来实现,因为兼容 Web 平台。
  3. 其内部实现:在平台侧通过 Isolate.run(),在 Web 侧通过 await null; 抽取单帧来执行函数。

三、Isolate 的工作原理

在 Dart 中,Isolate 是一种类似于线程的概念,可以独立于其他 Isolate 运行,并且具有自己的堆栈和内存空间。这使得 Isolate 可以并行执行代码,并且不会受到其他 Isolate 的影响。

3.1 Future 为何还是会导致卡顿?

有时候我们可能会困惑,为什么明明已经使用了 Future 来异步执行任务,还是会出现卡顿的现象。那是因为 Dart 是单线程的,如果在执行 Future 时遇到耗时的计算任务或者 I/O操作,这些操作会占用当前线程的资源,从而导致应用出现卡顿现象,影响用户体验。

相比之下,Isolate 可以实现多线程并发执行任务,可以利用多核 CPU,因此可以更有效地处理大规模的计算密集型任务、I/O 密集型任务以及处理需要大量计算的算法等。在 Isolate 中执行任务不会占用 UI 线程的资源,从而可以保证应用的流畅性和响应速度。

3.2 Isolate 的工作原理

Isolate 的工作原理是通过使用 Dart 的隔离机制来实现的。每个 Isolate 都运行在独立的隔离环境中,并且与其他 Isolate 共享代码的副本。这意味着Isolate之间不能直接共享数据,而必须使用消息传递机制来进行通信。

其实我们在执行 main() 时,就开始了主 Isolate 的运行,如下图所示:

【Flutter基础】Dart中的并发Isolate

3.3 Isolate 的生命周期

Isolate的生命周期可以分为三个阶段:创建、运行和终止。

  1. 创建阶段:使用 Isolate.spawn() 方法可以创建一个新的 Isolate,并且将一个函数作为参数传递给这个方法。这个函数将作为新的 Isolate 的入口点,也就是 Isolate 启动时第一个执行的函数。创建 Isolate 时还可以指定其他参数,例如 Isolate 的名称、是否共享代码等等。
  2. 运行阶段:一旦创建了 Isolate,它就会开始执行入口点函数,并且进入事件循环。在事件循环中,Isolate 会不断地从消息队列中获取消息,并且根据消息的类型执行相应的代码。Isolate 可以同时执行多个任务,并且可以通过消息传递机制来协调这些任务的执行顺序。
  3. 终止阶段:当 Isolate 完成了它的任务,或者由于某些原因需要停止时,可以调用 Isolate.kill() 方法来终止 Isolate。此时,Isolate 会立即停止执行,并且 Isolate 对象和所有与它相关的资源都会被释放。

【Flutter基础】Dart中的并发Isolate

3.4 Isolate 组

Dart 2.15 也就是 Flutter 2.8 版本之后,当一个 Isolate 调用了 Isolate.spawn(),两个 Isolate 将拥有同样的执行代码,并归入同一个 Isolate 组 中。Isolate 组会带来性能优化,例如新的 Isolate 会运行由 Isolate 组持有的代码,即共享代码调用。同时,Isolate.exit() 仅在对应的 Isolate 属于同一组时有效。

其原理是同一个 Isolate 组中的 Isolate 共享同一个堆,避免了对象的重复拷贝。这意味着生成一个新 Isolate 的速度提高了 100 倍,消耗的内存减少了 10-100 倍。

注意不要和前面的概念混淆,Isolate 仍然无法彼此共享内存,仍然需要消息传递。

四、使用场景

4.1 使用原则

  • 如果一段代码不会被中断,那么就直接使用正常的同步执行就行。
  • 如果代码段可以独立运行而不会影响应用程序的流畅性,建议使用 Future
  • 如果繁重的处理可能要花一些时间才能完成,而且会影响应用程序的流畅性,建议使用 Isolate

4.2 耗时衡量

通过原则来判断可能过于抽象,我们可以用耗时来衡量:

  • 对于耗时不超过 16ms 的操作推荐使用 Future
  • 对于耗时超过 16ms 以上的操作推荐使用 Isolate

至于为什么用 16ms 作为衡量呢,因为屏幕一帧的刷新间隔就是 16ms

compute API文档原文:

/// {@template flutter.foundation.compute.usecase} /// This is useful for operations that take longer than a few milliseconds, and /// which would therefore risk skipping frames. For tasks that will only take a /// few milliseconds, consider [SchedulerBinding.scheduleTask] instead. /// {@endtemplate}

五、结语

至此,我们完成了对 Isolate 概念和用法的认识。项目源码:Fitem/flutter_article

如果觉得这篇文章对你有所帮助的话,不要忘了一键三连哦,大家的点赞是我更新的动力🥰。最后祝大家周末愉快~

参考资料:

Isolate 的工作原理

Isolates in Flutter