Dart笔记:Isolate及其通信机制

news2024/9/8 23:12:06
Dart笔记
多隔离及其通信机制

- 文章信息 - Author: 李俊才 (jcLee95)
Visit me at CSDN: https://jclee95.blog.csdn.net
My WebSitehttp://thispage.tech/
Email: 291148484@163.com.
Shenzhen China
Address of this article:https://blog.csdn.net/qq_28550263/article/details/138823881
HuaWei:https://bbs.huaweicloud.com/blogs/XXXXXXXXXX

【介绍】:本文介绍Dart中多线程及其通信机制。

flutter-ljc


1. Isolate基础

Isolate是Dart提供的一种轻量级的并发编程模型,通过Isolate可以方便地编写高效、安全的多线程程序。在Dart中,Isolate是一种独立的执行线程,有自己的内存和事件循环。每个Isolate都在自己的内存堆中运行,不共享任何可变状态,因此Isolate之间的通信必须通过消息传递来完成。

Isolate具有以下特点:

  1. 独立性:每个Isolate都是完全独立的,有自己的内存空间和事件循环,不会被其他Isolate干扰。
  2. 隔离性:Isolate之间不共享任何可变状态,因此不会出现多线程编程中常见的竞态条件和死锁等问题。
  3. 通信方式:Isolate之间通过消息传递进行通信,消息可以是任意的Dart对象,但必须是不可变的。
  4. 并发性:多个Isolate可以并发执行,充分利用多核CPU的计算能力,提高程序的性能。
  5. 异常处理:每个Isolate都有自己的异常处理机制,不会影响其他Isolate的运行。

2. Isolate的创建方式

2.1 通过Isolate.spawn创建隔离

我们可以使用Isolate.spawn方法创建一个新的隔离(Isolate)。该方法的签名如下:

static Future<Isolate> spawn<T>(
    void entryPoint(T message), T message,
    {bool paused = false,
    bool errorsAreFatal = true,
    SendPort? onExit,
    SendPort? onError,
    ("2.3") String? debugName});

各个参数的含义如表所示:

参数名类型默认值描述
entryPointvoid Function(T)必需新隔离的入口函数,接收一个类型为T的消息参数。
messageT必需传递给新隔离的入口函数的消息,类型为T。
pausedboolfalse如果为true,则新隔离在启动时会被暂停。
errorsAreFatalbooltrue如果为true,隔离中未捕获的异常会导致隔离终止。
onExitSendPort?null隔离退出时的回调端口,可以用来接收隔离的退出信号。
onErrorSendPort?null隔离中发生未捕获异常时的回调端口,可以用来接收错误信息。
debugNameString?null隔离的调试名称,用于在调试时标识隔离。自Dart 2.3版本引入。

其中,我们使用该方法时主要关注的是entryPointmessage这两个参数,例如:

import 'dart:isolate';

void main() {
  // 创建新隔离,并传递一个字符串消息
  Isolate.spawn(workerIsolate, 'Hello from main isolate');
}

/// 新隔离的入口函数
///
/// [message] 主隔离传递过来的消息
void workerIsolate(String message) {
  print('New isolate received message: $message');
  // 在这里执行新隔离的任务
  // ...
}

在上面的代码中:

  1. 在主隔离(main函数)中,调用Isolate.spawn方法创建了一个新隔离。
  2. workerIsolate函数被指定为新隔离的入口函数,它接收一个字符串类型的消息参数。
  3. 字符串’Hello from main isolate’被作为消息参数传递给新隔离。
  4. 在新隔离中,workerIsolate函数被执行,并打印出接收到的消息。

通过Isolate.spawn方法,可以方便地创建新的隔离,并可以向新隔离传递初始化消息。新隔离将在独立的内存空间中运行,与主隔离相互隔离,从而实现并发执行的效果。

2.2 通过Isolate.spawnUri创建隔离

除了使用Isolate.spawn方法创建隔离外,Dart还提供了Isolate.spawnUri方法,可以通过指定一个URI来创建隔离。这个URI可以是一个Dart文件的路径或者一个包含Dart代码的字符串。

Isolate.spawnUri方法的签名如下:

static Future<Isolate> spawnUri(
    Uri uri, List<String> args, var message,
    {bool paused = false,
    SendPort? onExit,
    SendPort? onError,
    bool errorsAreFatal = true,
    bool? checked,
    Map<String, String>? environment,
    Uri? packageConfig,
    bool automaticPackageResolution = false,
    ("2.3") String? debugName});

各个参数的含义如表所示:

参数名类型默认值描述
uriUri必需包含隔离入口点的URI。可以是一个Dart文件的路径或包含Dart代码的URI。
argsList必需传递给隔离入口点的参数列表。
messagedynamic必需传递给隔离的初始消息。可以是任意类型的对象。
pausedboolfalse如果为true,则新隔离在启动时会被暂停。
onExitSendPort?null隔离退出时的回调端口,可以用来接收隔离的退出信号。
onErrorSendPort?null隔离中发生未捕获异常时的回调端口,可以用来接收错误信息。
errorsAreFatalbooltrue如果为true,隔离中未捕获的异常会导致隔离终止。
checkedbool?null表示是否启用运行时类型检查。默认为null,使用与当前隔离相同的设置。
environmentMap<String, String>?null传递给隔离的环境变量映射。
packageConfigUri?null包配置文件的URI。
automaticPackageResolutionboolfalse表示是否自动解析包。默认为false。
debugNameString?null隔离的调试名称,用于在调试时标识隔离。自Dart 2.3版本引入。

相比于Isolate.spawn方法,Isolate.spawnUri允许指定一个URI作为新隔离的入口点。例如:

import 'dart:isolate';

void main() async {
  // 指定包含隔离入口点的URI
  Uri uri = Uri.parse('package:my_package/worker.dart');

  // 创建隔离,并传递初始消息
  Isolate isolate = await Isolate.spawnUri(
    uri,
    ['Hello', 'from', 'main'],
    'Initial message',
    debugName: 'WorkerIsolate',
  );

  print('Isolate created: ${isolate.debugName}');
}

在上面的代码中:

  1. 通过Uri.parse方法指定了包含隔离入口点的URI,这里假设隔离的入口点位于package:my_package/worker.dart文件中。
  2. 调用Isolate.spawnUri方法创建隔离,传递了URI、参数列表和初始消息。
  3. 通过debugName参数指定了隔离的调试名称为 ‘WorkerIsolate’
  4. 创建隔离后,打印出隔离的调试名称。

在worker.dart文件中,需要定义隔离的入口点函数,例如:

// worker.dart
void main(List<String> args, dynamic message) {
  print('Worker isolate started with args: $args');
  print('Received initial message: $message');
  // 在这里执行隔离的任务
  // ...
}

在隔离的入口点函数中,可以接收传递的参数列表args和初始消息message。这里简单地打印出接收到的参数和消息。

通过Isolate.spawnUri方法,可以方便地将隔离的代码放在单独的Dart文件中,使代码结构更加清晰和模块化。同时,还可以向隔离传递参数和初始消息,方便隔离的初始化和配置。

需要注意的是,使用Isolate.spawnUri创建隔离时,需要确保URI指向的Dart文件是可访问的,并且具有正确的入口点函数签名。

Isolate.spawnUri提供了另一种创建隔离的方式,通过指定包含隔离代码的URI,可以将隔离的逻辑与主程序分离,提高代码的可读性和可维护性。同时,还支持传递参数和初始消息,使隔离的创建和配置更加灵活。

2.3 compute函数

Dart提供了一个便捷的函数compute,用于在后台isolate中执行耗时操作,并返回执行结果。compute函数会自动创建一个新的isolate,在其中运行指定的回调函数,并将结果返回给调用方。

Future<R> compute<M, R>(ComputeCallback<M, R> callback, M message, {String? debugLabel}) {
  return isolates.compute<M, R>(callback, message, debugLabel: debugLabel);
}

compute函数接受以下参数:

  • callback: 类型为ComputeCallback<M, R>,表示要在后台isolate中执行的回调函数。该函数接受一个类型为M的参数,并返回一个类型为R的结果。

  • message: 类型为M,表示要传递给回调函数的参数。

  • debugLabel: 可选参数,类型为String,用于为后台isolate指定一个调试标签。当进行性能分析时,该标签会与isolate产生的Timeline事件相关联,方便识别和定位问题。

使用compute函数的示例如下:

import 'package:flutter/foundation.dart';

void main() async {
  int result = await compute(fibonacci, 40);
  print('斐波那契数列第40位: $result');
}

/// 计算斐波那契数列的回调函数
///
/// [n] 要计算的斐波那契数列的位置
int fibonacci(int n) {
  if (n <= 0) {
    return 0;
  } else if (n == 1) {
    return 1;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

在上面的示例中:

  1. 导入了package:flutter/foundation.dart包,其中包含了compute函数的定义。
  2. 在main函数中,调用了compute函数,传入了fibonacci回调函数和参数40,表示要计算斐波那契数列的第40位。
  3. compute函数会自动创建一个新的isolate,并在其中执行fibonacci函数,计算斐波那契数列的第40位。
  4. 计算完成后,compute函数会将结果返回给调用方,并打印出结果。

fibonacci函数是一个递归函数,用于计算斐波那契数列的指定位置的值。由于斐波那契数列的计算是一个比较耗时的操作,特别是对于较大的位置值,使用compute函数可以将计算任务放到后台isolate中执行,避免阻塞主isolate,提高应用的响应性能。

传递给compute函数的回调函数以及参数都必须是可以在isolate之间传递的对象。大多数对象都可以在isolate之间传递,但是有一些例外情况需要注意,例如包含了不可传递状态的闭包等。

3. Isolate之间通信

3.1 单向通信

在Dart中,Isolate之间的单向通信可以通过SendPort和ReceivePort来实现。发送端通过SendPort将消息发送给接收端,接收端通过ReceivePort接收消息。

下面是一个示例代码,演示了如何在Isolate之间进行单向通信:

import 'dart:isolate';

void main() {
  startSingleDirectionExample();
}

/// 单向通信示例函数
Future<void> startSingleDirectionExample() async {
  print('SingleDirection start----------');
  String mainDebugName = Isolate.current.debugName!;
  print('[$mainDebugName]为主线程');

  // 创建主线程的ReceivePort
  ReceivePort mainReceivePort = ReceivePort();

  // 创建新线程,并将主线程的ReceivePort传递给新线程
  await Isolate.spawn(
    workerThread,
    mainReceivePort.sendPort,
    debugName: 'WorkerIsolate',
  );

  // 监听来自新线程的消息
  await for (var message in mainReceivePort) {
    if (message == null) {
      break;
    }
    print('[$mainDebugName]收到了来自新线程的消息: $message');
  }

  print('SingleDirection end----------');
}

/// 新线程的入口函数
///
/// [mainSendPort] 主线程传递过来的SendPort
void workerThread(SendPort mainSendPort) {
  String workerDebugName = Isolate.current.debugName!;
  print('[$workerDebugName]为新线程');

  // 向主线程发送消息
  mainSendPort.send('Hello from $workerDebugName');
  mainSendPort.send('How are you?');
  mainSendPort.send('Goodbye!');

  // 发送null值,表示不再有新的消息了
  mainSendPort.send(null);

  // 关闭新线程
  Isolate.exit();
}

在上面的代码中:

  1. 在主线程(main函数)中,创建了一个ReceivePort对象mainReceivePort,用于接收来自新线程的消息。
  2. 调用Isolate.spawn方法创建一个新线程,并将主线程的SendPort对象mainReceivePort.sendPort传递给新线程的入口函数workerThread。同时,通过debugName参数指定新线程的调试名称为**‘WorkerIsolate’**。
  3. 在主线程中,使用await for循环监听mainReceivePort上的消息。每当收到新线程发送的非null消息时,就会打印出消息内容。如果收到null消息,则表示新线程不再发送消息,此时跳出循环。
  4. 在新线程(workerThread函数)中,通过传递过来的SendPort对象mainSendPort,向主线程发送了三条非null的消息。
  5. 在发送完非null的消息后,新线程额外发送了一个null值,表示不再有新的消息了。
  6. 最后,新线程调用 **Isolate.exit()**方法关闭自己。
    运行该代码,输出结果如下:
SingleDirection start----------
[main]为主线程
[WorkerIsolate]为新线程
[main]收到了来自新线程的消息: Hello from WorkerIsolate
[main]收到了来自新线程的消息: How are you?
[main]收到了来自新线程的消息: Goodbye!
SingleDirection end----------

从输出结果可以看到,主线程成功接收到了新线程发送的三条非null的消息,并在接收到null消息后跳出了循环,继续执行后面的代码,打印出了'SingleDirection end----------'

这就是Isolate之间单向通信的基本实现。发送端通过SendPort对象将消息发送给接收端,接收端通过ReceivePort对象接收消息。当发送端发送null值时,表示不再有新的消息,接收端可以根据这个信号来结束接收循环。

需要注意的是,在新线程中发送完消息后,需要显式关闭新线程,以释放资源。可以通过调用Isolate.exit()方法来关闭新线程。

3.2 双向通信

import 'dart:isolate';

void main() {
  startMultiThreadExample();
}

/// 多线程示例函数
Future<void> startMultiThreadExample() async {
  print('mutiTheread start----------');
  String debugName = Isolate.current.debugName!;
  print('[$debugName]为当前线程');

  // 创建主线程的ReceivePort和SendPort
  ReceivePort mainReceivePort = ReceivePort();
  SendPort mainSendPort = mainReceivePort.sendPort;

  // 创建新线程,并将主线程的SendPort传递给新线程
  Isolate.spawn(workerThread, mainSendPort);

  // 等待新线程返回其SendPort
  SendPort workerSendPort = await mainReceivePort.first;

  // 向新线程发送消息,并等待回复
  var reply1 = await sendAndReceive<String>(workerSendPort, 'Hello');
  print('[$debugName]接收到:$reply1');

  var reply2 = await sendAndReceive<String>(workerSendPort, 'World');
  print('[$debugName]接收到:$reply2');
  print('mutiTheread end----------');
}

/// 新线程的入口函数
///
/// [mainSendPort] 主线程传递过来的SendPort
workerThread(SendPort mainSendPort) async {
  String debugName = Isolate.current.debugName!;
  print('[$debugName]为当前线程');

  // 创建新线程的ReceivePort和SendPort
  ReceivePort workerReceivePort = ReceivePort();
  SendPort workerSendPort = workerReceivePort.sendPort;

  // 将新线程的SendPort发送给主线程
  mainSendPort.send(workerSendPort);

  // 持续监听新线程的消息
  await for (var message in workerReceivePort) {
    // 检查消息格式是否正确
    if (message is List && message.length == 2) {
      var data = message[0];
      // 检查消息类型是否为字符串
      if (data is String) {
        print('[$debugName]收到了来自主线程的消息:$data');
        SendPort replyPort = message[1];
        // 给主线程回复消息
        replyPort.send(data);
      } else {
        print('[$debugName]收到了无效的消息类型:${data.runtimeType}');
      }
    } else {
      print('[$debugName]收到了无效的消息格式');
    }
  }
}

/// 向指定的SendPort发送消息,并等待回复
///
/// [targetPort] 目标SendPort
/// [message] 要发送的消息
/// 返回: 收到的回复消息
Future<T> sendAndReceive<T>(SendPort targetPort, T message) {
  String debugName = Isolate.current.debugName!;
  print('[$debugName]发送消息给新线程:$message');

  // 创建接收回复消息的ReceivePort
  ReceivePort responsePort = ReceivePort();

  // 发送消息给目标SendPort,并携带接收回复的SendPort
  targetPort.send([message, responsePort.sendPort]);

  // 等待回复消息,并检查类型是否匹配
  return responsePort.first.then((value) {
    if (value is T) {
      return value;
    } else {
      throw Exception('接收到的消息类型与预期不符');
    }
  });
}
mutiTheread start----------
[main]为当前线程
[workerThread]为当前线程
[main]发送消息给新线程:Hello
[workerThread]收到了来自主线程的消息:Hello
[main]接收到:Hello
[main]发送消息给新线程:World
[workerThread]收到了来自主线程的消息:World
[main]接收到:World
mutiTheread end----------

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1916965.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【合并两个有序数组】

合并两个有序数组 一、题目二、普通解法三、双指针 一、题目 二、普通解法 先合并后排序 补充:js合并数组方法详见https://blog.csdn.net/ACCPluzhiqi/article/details/131702269?fromshareblogdetail js排序方法见http://t.csdnimg.cn/wVCOP 时间复杂度&#xff1a;O(mn)…

【DevOps】在云原生时代的角色与重要性探索

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《未来已来&#xff1a;云原生之旅》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、什么是云原生 2、云原生的核心特性 3、什么是DevOps…

c++获取用户的输入并格式化

一行单个数据 string str; cin>>str;一行固定多个且空格隔开 int n, m; cin >> n >> m;在输入时&#xff0c;会自动做格式装换。 一行不固定多个且空格隔开 #include <sstream> #include <string>string str; getline(cin, str); stringstr…

FFmpeg开发笔记(四十五)使用SRT Streamer开启APP直播推流

SRT Streamer是一个安卓手机端的开源SRT协议直播推流框架&#xff0c;可用于RTMP直播和SRT直播。SRT Streamer支持的视频编码包括H264、H265等等&#xff0c;支持的音频编码包括AAC、OPUS等等&#xff0c;可谓功能强大的APP直播框架。 相比之下&#xff0c;另一款APP直播框架RT…

【系统架构设计师】九、软件工程(软件开发生命周期|McCabe度量法|系统转换|系统维护|净室软件工程|基于构件的软件工程)

目录 九、软件开发生命周期和工具 十、McCabe度量法 十一、系统转换 11.1 遗留系统 11.2 系统转换 11.3 系统维护 十二、净室软件工程 十三、基于构件的软件工程 13.1 构件特征 13.2 构件模型要素 13.3 CBSE过程 13.4 构件组装 相关推荐 历年真题练习 九、软件开…

Django项目创建的基本准备工作【4】

【 一 】软件开发模式 官话下面 人话 瀑布开发就是将什东西都定义好了在进行开发对吧 敏捷就是进行模块化一样 分批进行 规定一个时间段完成什么样的功能。 总结来说&#xff0c;瀑布开发强调在项目开始之前进行详细的计划和准备&#xff0c;并按照预定的顺序逐步进行&#x…

Elasticseach学习

概念 是一个开源的分布式搜索引擎&#xff0c;可以应用于搜索、日志监控等 倒排索引 正向索引&#xff1a;基于文档id创建索引。查询词条时必须先找到文档&#xff0c;而后判断是否包含词条 倒排索引&#xff1a;对文档内容分词&#xff0c;对词条创建索引&#xff0c;并记录…

前端面试题44(JavaScript几种排序的变种或高级应用)

1. 快速排序的变种 三轴快排 (Three-Way QuickSort)&#xff1a;处理大量重复元素时更为高效&#xff0c;通过维护三个区域来避免重复元素的重复比较和交换。平衡快排 (Balanced QuickSort)&#xff1a;通过随机选取或使用中位数作为枢轴&#xff0c;以减少最坏情况下的性能退…

重命名文件的方法有哪些?重命名文件的工具有哪些?

在日常的计算机使用过程中&#xff0c;重命名文件是一项常见但至关重要的任务。无论是为了更好地组织文件、修复命名错误&#xff0c;还是简化文件管理流程&#xff0c;掌握正确的重命名方法和工具都能显著提升效率。 本文将探讨多种重命名文件的方法&#xff0c;同时介绍几款高…

传言称 iPhone 16 Pro 将支持 40W 快速充电和 20W MagSafe

目前&#xff0c;iPhone 15 和 iPhone 15 Pro 机型使用合适的 USB-C 电源适配器可实现高达 27W 的峰值充电速度&#xff0c;而 Apple 和授权第三方的官方 MagSafe 充电器可以高达 15W 的功率为 iPhone 15 机型进行无线充电。所有四款 iPhone 15 机型均可使用 20W 或更高功率的电…

ArduPilot开源飞控之AP_Mount_Siyi

ArduPilot开源飞控之AP_Mount_Siyi 1. 源由2. 框架设计2.1 类和继承2.2 公共方法2.3 保护方法2.4 私有成员和方法2.5 解析状态2.6 重要成员变量 3. 重要方法3.1 AP_Mount_Siyi::init3.2 AP_Mount_Siyi::update3.3 AP_Mount_Siyi::read_incoming_packets3.4 AP_Mount_Siyi::proc…

linux登入提示信息

目录 1.Linux 登录提示信息在操作系统中扮演着重要的角色 安全性提醒 欢迎信息 系统状态通知 政策和使用条款 技术支持信息 更新和变更通知 2.配置文件介绍 3.编辑配置文件 4.效果展示 修改前 修改后 目录 1.Linux 登录提示信息在操作系统中扮演着重要的角色 安…

vue 数据类型

文章目录 ref 创建&#xff1a;基本类型的响应式数据reactive 创建&#xff1a;对象类型的响应式数据ref 创建&#xff1a;对象类型的响应式数据ref 对比 reactive将一个响应式对象中的每一个属性&#xff0c;转换为ref对象(toRefs 与 toRef)computed (根据计算进行修改) ref 创…

Go语言---并发编程以及资源竞争(goroutine、runtime)

并发和并行 并行(parallel):指在同一时刻&#xff0c;有多条指令在多个处理器上同时执行。 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行&#xff0c;使得在宏观上具有多个进程同时执行的效果&#xff0c;但在微观上并不是同时执行的&a…

华为HCIP Datacom H12-821 卷34

1.单选题 防火墙默认已经创建了一些安全区域,以下哪一个安全区域不是防火墙上默认存在的? A、Trust B、DMZ C、Internet D、Local 正确答案&#xff1a; C 解析&#xff1a; 防火墙默认情况下为我们提供了三个安全区域&#xff0c;分别是 Trust、DMZ和Untrust 2.判断题 …

8. Python3 pandas数据分析处理库

11.1 pandas的数据结构 pandas的数据结构如下图所示&#xff1a; pandas的几种数据结构有内在联系&#xff0c;可以吧DataFrame看作Series的容器&#xff0c;把Panel看作DataFrame的容器。可以像操作字典那样在这些数据结构中插入或者移除数据对象。在介绍这些数据结构之前&am…

力扣-dfs

何为深度优先搜索算法&#xff1f; 深度优先搜索算法&#xff0c;即DFS。就是找一个点&#xff0c;往下搜索&#xff0c;搜索到尽头再折回&#xff0c;走下一个路口。 695.岛屿的最大面积 695. 岛屿的最大面积 题目 给你一个大小为 m x n 的二进制矩阵 grid 。 岛屿 是由一些相…

【Git基本操作】添加文件 | 修改文件 | 及其各场景下.git目录树的变化

目录 1. 添加文件&add操作和commit操作 2. .git树状目录的变化 3. git其他操作 4. 修改文件 4.1 git status 4.2 git diff 1. 添加文件&add操作和commit操作 add操作&#xff1a;将工作区中所有文件的修改内容 添加进版本库的暂存区中。commit操作&#xff1a;…

TortoiseSVN-VisualSVNServer-软件代码文本资源版本控制管理-版本比较及差异文件

文章目录 1.VisualSVNServer安装2.TortoiseSVN安装2.1.检出2.2.提交资源2.3.更新资源2.4.返回版本2.5.比较软件可更改2.6.在此创建版本库3.TortoiseSVN版本差异文件1.VisualSVNServer安装 从官网下载,或者csdn下载链接: https://download.csdn.net/download/m0_67316550/8952…

Python酷库之旅-第三方库Pandas(015)

目录 一、用法精讲 37、pandas.read_sql函数 37-1、语法 37-2、参数 37-3、功能 37-4、返回值 37-5、说明 37-6、用法 37-6-1、数据准备 37-6-2、代码示例 37-6-3、结果输出 38、pandas.DataFrame.to_sql函数 38-1、语法 38-2、参数 38-3、功能 38-4、返回值 …