Dart笔记:Isolate及其通信机制

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 {// 指定包含隔离入口点的URIUri 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]为主线程');// 创建主线程的ReceivePortReceivePort 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和SendPortReceivePort mainReceivePort = ReceivePort();SendPort mainSendPort = mainReceivePort.sendPort;// 创建新线程,并将主线程的SendPort传递给新线程Isolate.spawn(workerThread, mainSendPort);// 等待新线程返回其SendPortSendPort 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和SendPortReceivePort 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');// 创建接收回复消息的ReceivePortReceivePort responsePort = ReceivePort();// 发送消息给目标SendPort,并携带接收回复的SendPorttargetPort.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.rhkb.cn/news/374093.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里云通义千问开源两款语音基座模型分别是SenseVoice和CosyVoice

阿里巴巴近期发布了开源语音大模型项目FunAudioLLM&#xff0c;该项目包含了两个核心模型&#xff1a;SenseVoice和CosyVoice。可以精准多语言识别并且进行语音克隆。 SenseVoice&#xff1a;精准多语言识别与情感辨识 SenseVoice主要致力于高精度多语言语音识别、情感辨识和…

《算法笔记》总结No.6——贪心

一.简单贪心 贪心法是求解一类最优化问题的方法&#xff0c;它总是考虑在当前状态下局部最优(或较优)之后&#xff0c;来使全局的结果达到最优(或较优)的策略。显然&#xff0c;如果采取较优而非最优的策略(最优策略可能不存在或是不易想到)&#xff0c;得到的全局结果也无法是…

webGL可用的14种3D文件格式,但要具体问题具体分析。

hello&#xff0c;我威斯数据&#xff0c;你在网上看到的各种炫酷的3d交互效果&#xff0c;背后都必须有三维文件支撑&#xff0c;就好比你网页的时候&#xff0c;得有设计稿源文件一样。WebGL是一种基于OpenGL ES 2.0标准的3D图形库&#xff0c;可以在网页上实现硬件加速的3D图…

无人机之飞行规划与管理篇

无人机飞行规划与管理是确保无人机安全、高效且符合法规的运行的关键步骤。这一过程包括了对飞行任务的详细安排、航线的设定以及风险的评估和管理。下面简述这一过程的主要环节&#xff1a; 一、飞行目的和任务确定 在规划之初&#xff0c;必须明确无人机的飞行目的&#xf…

ETAS工具导入Com Arxml修改步骤

文章目录 前言Confgen之前的更改Confgen之后的修改CANCanIfComComMEcuM修改CanNmCanSMDCMCanTp生成RTE过程报错修改DEXT-诊断文件修改Extract问题总结前言 通讯协议栈开发一般通过导入DBC实现,ETAS工具本身导入DBC也是生成arxml后执行cfggen,本文介绍直接导入客户提供的arxml…

如何保证Redis缓存和数据库的数据一致性

前言 如果项目业务处于起步阶段&#xff0c;流量非常小&#xff0c;那无论是读请求还是写请求&#xff0c;直接操作数据库即可&#xff0c;这时架构模型是这样的&#xff1a; 但随着业务量的增长&#xff0c;项目业务请求量越来越大&#xff0c;这时如果每次都从数据库中读数据…

链表 OJ(一)

移除链表元素 题目连接&#xff1a; https://leetcode.cn/problems/remove-linked-list-elements/description/ 使用双指针法&#xff0c;开始时&#xff0c;一个指针指向头节点&#xff0c;另一个指针指向头节点的下一个结点&#xff0c;然后开始遍历链表删除结点。 这里要注…

【SGX系列教程】(五)enclave多线程测试,以及EPC内存测试

文章目录 一. 概述二. 原理分析2.1 多线程在Enclave中的实现流程2.2 多线程和EPC内存分配之间的冲突2.3 解决多线程和EPC内存分配冲突的策略 三. 源码分析3.1 代码结构3.2 源码3.2.1 App文件夹3.2.2 Enclave文件夹3.2.3 Makefile 3.3 总结 四.感谢支持 一. 概述 在Intel SGX环境…

HarmonyOS(43) @BuilderParam标签使用指南

BuilderParam BuilderParam使用举例定义模板定义具体实现BuilderParam初始化 demo源码参考资料 BuilderParam 该标签有的作用有点类似于设计模式中的模板模式&#xff0c;类似于指定一个UI占位符&#xff0c;具体的实现交给具体的Builder&#xff0c;顾名思义&#xff0c;可以…

【算法】排序算法介绍 附带C#和Python实现代码

1. 冒泡排序(Bubble Sort) 2. 选择排序(Selection Sort) 3. 插入排序(Insertion Sort) 4. 归并排序(Merge Sort) 5. 快速排序(Quick Sort) 排序算法是计算机科学中的一个基础而重要的部分,用于将一组数据按照一定的顺序排列。下面介绍几种常见的排序算法,…

可控学习综述:信息检索中的方法、应用和挑战

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

YOLOv10改进 | Conv篇 | 利用YOLO-MS的MSBlock轻量化网络结构(既轻量又长点)

一、本文介绍 本文给大家带来的改进机制是利用YOLO-MS提出的一种针对于实时目标检测的MSBlock模块(其其实不能算是Conv但是其应该是一整个模块)&#xff0c;我们将其用于C2f中组合出一种新的结构&#xff0c;来替换我们网络中的模块可以达到一种轻量化的作用&#xff0c;我将其…

【Python基础】代码如何打包成exe可执行文件

本文收录于 《一起学Python趣味编程》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、安装PyInstaller三、使用PyInstaller打包四、验证打包是否成功五、总结 一、前言 本文介绍如何…

SFUZZ模糊测试平台全新升级,从标准到实践助力车企安全出海

开源网安模糊测试平台SFuzz全新升级&#xff0c;参照各国相关标准要求进行针对性建设&#xff0c;可为智能网联汽车信息安全测试提供更为强大的工具支持。SFuzz向被测系统输入大量随机数据&#xff0c;模拟各种异常情况&#xff0c;可以发现被测系统内潜在的缺陷和漏洞&#xf…

ChatGPT提问获取高质量答案的艺术PDF下载书籍推荐分享

ChatGPT高质量prompt技巧分享pdf&#xff0c; ChatGPT提问获取高质量答案的艺术pdf。本书是一本全面的指南&#xff0c;介绍了各种 Prompt 技术的理解和利用&#xff0c;用于从 ChatGPTmiki sharing中生成高质量的答案。我们将探讨如何使用不同的 Prompt 工程技术来实现不同的目…

C语言——结构体

一、定义和使用结构体 1.1概述 前面我们见过的数据类型&#xff0c;比如int,float,char等是在程序中简单的使用&#xff0c;如果我们要根据自己的需求来建立一些复杂的数据&#xff0c;就需要用到结构体。 例如&#xff0c;一个学生的学号&#xff0c;姓名&#xff0c;性别&am…

Python 利用pandas处理CSV文件(DataFrame的基础用法)

前面介绍过通过Python标准库中的CSV模块处理CSV文件&#xff1a; Python 利用CSV模块处理数据 相比CSV模块&#xff0c;pandas的功能更加强大&#xff0c;本文将简单介绍如何通过pandas来处理CSV文件。 文章目录 一、pandas简介二、用法示例2.1 读取CSV文件2.1.1 read_csv参数…

Python 视频的色彩转换

这篇教学会介绍使用OpenCV 的cvtcolor() 方法&#xff0c;将视频的色彩模型从RGB 转换为灰阶、HLS、HSV...等。 因为程式中的OpenCV 会需要使用镜头或GPU&#xff0c;所以请使用本机环境( 参考&#xff1a;使用Python 虚拟环境) 或使用Anaconda Jupyter 进行实作( 参考&#x…

火柴棒图python绘画

使用Python绘制二项分布的概率质量函数&#xff08;PMF&#xff09; 在这篇博客中&#xff0c;我们将探讨如何使用Python中的scipy库和matplotlib库来绘制二项分布的概率质量函数&#xff08;PMF&#xff09;。二项分布是统计学中常见的离散概率分布&#xff0c;描述了在固定次…

MUNIK解读ISO26262--系统架构

功能安全之系统阶段-系统架构 我们来浅析下功能安全系统阶段重要话题——“系统架构” 目录概览&#xff1a; 系统架构的作用系统架构类型系统架构层级的相关安全机制梳理 1.系统架构的作用 架构的思维包括抽象思维、分层思维、结构化思维和演化思维。通过将复杂系统分解…