Flutter:多线程Isolate的简单使用

在flutter中如果要使用线程,需要借助Isolate来实现。

简介

在Flutter中,Isolate是一种轻量级的线程解决方案,用于在应用程序中执行并发任务。Isolate可以被认为是独立于主线程的工作单元,它们可以在后台执行任务而不会阻塞应用程序的用户界面。

Isolate提供了多线程编程的能力,允许开发者在应用程序中同时执行多个任务,从而提高应用程序的性能和响应能力。每个Isolate都有自己独立的内存空间和执行环境,它们之间可以通过消息传递进行通信。

在Flutter中,可以使用dart:isolate库来创建和管理Isolate。通过创建一个Isolate对象,可以指定要执行的任务代码。Isolate可以执行耗时的计算、网络请求、文件操作等任务,而不会阻塞应用程序的主线程。

与其他线程解决方案相比,Isolate的一个重要特点是它们之间的内存是隔离的,这意味着每个Isolate都有自己独立的内存空间,它们之间不能直接共享数据。为了在Isolate之间传递数据,可以使用消息传递机制,即将数据打包成消息发送给目标Isolate,并在接收端解包处理。

使用Isolate可以提高应用程序的性能和响应能力,特别是在处理大量计算密集型任务或需要与外部资源进行交互的场景中。然而,需要注意的是,Isolate的创建和销毁都会带来一定的开销,因此在使用Isolate时需要权衡资源消耗和性能提升之间的平衡。

总而言之,Isolate是Flutter中的一种轻量级线程解决方案,用于在应用程序中执行并发任务。它们可以独立于主线程执行任务,并通过消息传递机制进行通信。使用Isolate可以提高应用程序的性能和响应能力,特别是在处理计算密集型任务或需要与外部资源进行交互的场景中。

基本使用

匿名函数使用

void _incrementCounter() {setState(() {_counter++;});// 匿名线程Isolate.spawn((message) {print("匿名函数线程:$message");}, '哈哈哈');
}

参数会被传递到匿名函数中,并被匿名函数所使用。

普通函数

 void _incrementCounter() {setState(() {_counter++;});// 普通线程Isolate.spawn(newThread1, "普通线程");}
// 创建一个额外线程
void newThread1(String message){print(message);
}

这个一定要注意,在flutter中使用时newThread1不能写在主进程所在的类里。否则会提示[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message: object is unsendable - Library:'dart:async' Class: _AsyncCompleter@4048458 (see restrictions listed at SendPort.send() documentation for more information)

子线程向主线程通信

// 创建一个额外线程
void newThread1(SendPort mainThreadPort) {int num = 0;Timer.periodic(const Duration(seconds: 1), (timer) {// 每隔1秒num加1num += 1;mainThreadPort.send(num);if (num == 10) {// 向主进程发消息执行完成mainThreadPort.send('stop');}});
}class _MyHomePageState extends State<MyHomePage> {final receivePort = ReceivePort();void _incrementCounter() async {// 子线程final isolate = await Isolate.spawn(newThread1, receivePort.sendPort);// 监听子线程发送的消息receivePort.listen((message) {print("子线程发送的消息:$message");if (message == 'stop') {// 执行完成则停止显示print("线程执行完成");isolate.kill(priority: Isolate.immediate);}});}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(backgroundColor: Theme.of(context).colorScheme.inversePrimary,title: Text(widget.title),),body: const Center(child: Text("多线程")),floatingActionButton: FloatingActionButton(onPressed: _incrementCounter,tooltip: 'Increment',child: const Icon(Icons.add),), // This trailing comma makes auto-formatting nicer for build methods.);}
}

在这里插入图片描述
主线程向子线程通信
在子线程执行期间,主线程也可以向子线程进行通信。基于上面的代码,进行简单修改

void newThread1(SendPort mainThreadPort) {int num = 0;ReceivePort receivePort = ReceivePort();Timer.periodic(const Duration(seconds: 1), (timer) {// 每隔1秒num加1num += 1;mainThreadPort.send(num);if(num==1){// 跟主进程类似,将receivePort.sendPort传递给主进程,主进程才能向子进程通信mainThreadPort.send(receivePort.sendPort);}if (num == 10) {// 向主进程发消息执行完成mainThreadPort.send('stop');}});// 接收主进程发送的消息receivePort.listen((message) {print("收到了主进程的消息:$message");});
}
  void _incrementCounter() async {// 子线程final isolate = await Isolate.spawn(newThread1, receivePort.sendPort);late SendPort childSendPort;// 监听子线程发送的消息receivePort.listen((message) {print("子线程发送的消息:$message");if (message is SendPort) {childSendPort = message;}if (message == 6) {childSendPort.send("加油,马上完成了");}if (message == 'stop') {// 执行完成则停止显示print("线程执行完成");isolate.kill(priority: Isolate.immediate);}});}

怎么理解呢?

可以理解为:我找了一个人干活,我把自己的手机号给他了,但是忘记跟他要手机号了,因此只能他给我打电话,而我不能给他打电话。
当他向我打电话后,我存下了他的手机号,后续我就可以给他打电话了。
也就是下面内容:
子线程要想向主线程通信,需要获取到主线程的receivePort.sendPort,因此在子线程创建时,主线程将自己的receivePort.sendPort传递给子线程。
同理,主线程要想向子线程通信也需要拿到子线程的receivePort.sendPort,因此在子线程向主线程通信后,需要将自己的端口号给主线程。

只有主线程、子线程都拿到对方的receivePort.sendPort 才可以进行互相通信。

在这里插入图片描述

线程池

也不知道自己写的这个demo算不算,毕竟没怎么接触过线程池。

简介

线程池是一种管理和重用线程的技术。它由一个线程集合组成,这些线程可以被用来执行多个任务,而不需要为每个任务都创建一个新的线程。

线程池中有一个任务队列,用于存储待执行的任务。当有新的任务到达时,线程池会从任务队列中取出一个线程来执行任务。当任务执行完成后,线程会返回线程池,可以继续执行新的任务。

使用线程池有以下几个优点:

  1. 提高系统性能:由于线程的创建和销毁开销较大,线程池可以避免频繁地创建和销毁线程,从而提高系统的性能。
  2. 提高任务执行效率:线程池可以并发地执行多个任务,从而提高任务的执行效率。
  3. 控制并发度:线程池可以限制同时执行的线程数,从而控制任务的并发度,避免资源过度占用。

案例
这个案例有三个角色:

  • 老板,主线程,用于和工头线程进行沟通
  • 工头,子线程,根据老板发放的任务来招工、向工人安排工作、与工人沟通
  • 工人,若干个子线程,用来干活

工作流程

[{ "taskId":"a1", "number":10},{ "taskId":"a2","number":15},{ "taskId":"a3","number":20},{"taskId":"a4","number":5}]

有4个任务,3个工人。工人开始工作(每隔1秒打印一个数字),当完成工作后(打印的数字等于number)工人向工头汇报,工头给完成工作的工人,安排新的工作。

效果图
在这里插入图片描述
在这里插入图片描述
主进程(老板)

class _MyHomePageState extends State<MyHomePage> {// 主线程本身的端口final receivePort = ReceivePort();// 记录子线程的端口late SendPort foremanPort;late Foreman foreman;@overridevoid initState() {super.initState();// 创建子线程,并将主线程的端口号给子线程Foreman.getInstance(receivePort.sendPort);// 监听子线程传来的消息receivePort.listen((message) {if (message is SendPort) {foremanPort = message;} else {// 普通消息print("子线程发来的消息:$message");}});}void _incrementCounter() async {// 向子线程发消息foremanPort.send({"from":"main",'type':'initWorker','data':[{"taskId":"a1","number":10},{"taskId":"a2","number":15},{"taskId":"a3","number":20},{"taskId":"a4","number":5}]});}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(backgroundColor: Theme.of(context).colorScheme.inversePrimary,title: Text(widget.title),),body: const Center(child: Text("多线程")),floatingActionButton: FloatingActionButton(onPressed: _incrementCounter,tooltip: 'Increment',child: const Icon(Icons.add),), // This trailing comma makes auto-formatting nicer for build methods.);}
}

子线程(工头和工人)

// 包工头import 'dart:async';
import 'dart:isolate';class Foreman {// 记录主线程的通信端口late SendPort _mainPort;// 包工头本身的通信端口final ReceivePort _foremanPort = ReceivePort();// 任务列表List _tasks = [];// 工人列表final Map _workerMap = {};// 记录当前的任务下标int _taskIndex = 0;// 创建一个私有类型的实例static Foreman? _instance;// 私有化构造函数,避免通过构造函数创建实例Foreman._init();// 获取类的实例static Foreman getInstance(SendPort mainPort) {// 未初始化,则进行初始化_instance ??= Foreman._init();// 记录一下主线程的端口_instance!._mainPort = mainPort;// 向主进程进行通信mainPort.send(_instance!._foremanPort.sendPort);// 监听主进程发来的消息_instance!._foremanPort.listen((message) {if (message['from'] == 'main') {// 收到来自主线程的消息print("主线程发来的信息:$message");// 工头开始招人干活if (message['type'] == 'initWorker') {_instance!._tasks = message['data'];_instance!._initWorker();}}if (message['from'] == 'worker') {// 接收来自工人的端口var worker = _instance!._workerMap[message['workerId']];// 监听来自工人的消息if (message['type'] == 'port') {worker['port'] = message['port'];print("收到工人${message['workerId']}的端口");}if (message['type'] == 'finish') {// 有工人完成工作了,给他安排新工作if (_instance!._taskIndex < _instance!._tasks.length) {var task = _instance!._tasks[_instance!._taskIndex];// 说明还有工作,安排工作worker['port'].send({'workerId': worker['workerId'],'taskId': task["taskId"],'num': task['number']});// 更新任务下标_instance!._taskIndex += 1;}}}});return _instance!;}// 创建工人列表_initWorker() {print("任务列表:$_tasks");// 默认最大3个线程int size = _tasks.length > 3 ? 3 : _tasks.length;// 创建工人线程,将工头的端口给工人for (int i = 0; i < size; i++) {var isolate = Isolate.spawn(work, {"port": _foremanPort.sendPort,"workerId": "worker_$i","taskId": _tasks[i]['taskId'],"num": _tasks[i]['number']});// 记录工人_workerMap["worker_$i"] = {"workerId": "worker_$i", "worker": isolate};}// 记录下标_taskIndex = size;}
}// 工人干活函数
void work(message) {// 记录工头的通信端口SendPort foremanPort = message['port'];// 工人本身的通信端口ReceivePort workerPort = ReceivePort();print("message:工人${message['workerId']},开始执行任务${message['taskId']}");// 向工头发送消息foremanPort.send({"from": "worker","type": 'port',"workerId": message['workerId'],"port": workerPort.sendPort});// 开始干活toDo(message, foremanPort);// 监听来自工头的消息workerPort.listen((newMessage) {print("收到来自工头的消息:$newMessage");toDo(newMessage, foremanPort);});
}// 工人干的活
void toDo(message, foremanPort) {int num = 0;Timer.periodic(const Duration(seconds: 1), (Timer timer) {print("工人${message['workerId']},任务${message['taskId']},$num");num += 1;if (num == message['num']) {timer.cancel();print("工人${message['workerId']},任务:${message['taskId']}完成");// 向工头汇报任务完成foremanPort.send({"from": "worker","type": "finish","workerId": message['workerId'],});}});
}

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

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

相关文章

vite项目配置vite.config.ts在打包过程中去除日志

在生产环境上&#xff0c;务必要将日志清除干净&#xff0c;其因有二&#xff0c;在webgis系统中&#xff0c;有很多几何数据&#xff0c;体积大、数量多&#xff0c;很容易引起系统卡顿&#xff1b;清除log后&#xff0c;系统看着舒服&#xff0c;协同开发有很多无聊的日志&am…

【Redis】前言--redis产生的背景以及过程

一.介绍 为什么会出现Redis这个中间件&#xff0c;从原始的磁盘存储到Redis中间又发生了哪些事&#xff0c;下面进入正题 二.发展史 2.1 磁盘存储 最早的时候都是以磁盘进行数据存储&#xff0c;每个磁盘都有一个磁道。每个磁道有很多扇区&#xff0c;一个扇区接近512Byte。…

【送书活动二期】Java和MySQL数据库中关于小数的保存问题

之前总结过一篇文章mysql数据库&#xff1a;decimal类型与decimal长度用法详解&#xff0c;主要是个人学习期间遇到的mysql中关于decimal字段的详解&#xff0c;最近在群里遇到一个小伙伴提出的问题&#xff0c;也有部分涉及&#xff0c;今天就再大致总结一下Java和MySQL数据库…

ArcGIS如何处理并加载Excel中坐标数据?

做GIS行业的各位肯定免不了跟数据打交道&#xff0c;其中数据的处理说复杂也复杂&#xff0c;因为我们要花时间去做数据的转换及调整工作&#xff0c;那说简单也简单&#xff0c;因为我们有很多的工具可以使用&#xff0c;那么今天我就给大家带来处理Excel中的GIS数据中的其中一…

Windows 10和11的一个专用的设置菜单,让清理空间变得方便快捷

需要在Windows电脑上释放一些磁盘空间吗?Windows 10和Windows 11都提供了一个专用的设置菜单,使过程更容易。从该菜单中,你可以查看设备上使用了多少空间以及内容类型。 Windows中的“存储”设置还允许你快速清除空间,并启用“存储感知”自动删除临时文件和回收站项目。这…

Toast UI Editor上传图片到Flask

Toast UI Editor国内文档几乎搜不到&#xff0c;国外文档也写得不是特别项目&#xff0c;没有太多举例的demo。一开始选择使用这个就是因为UI好看。不过看看源码把思路滤清了。 他会给把图片转成Base64&#xff0c;到时候发表单直接丢过去就行了&#xff0c;blob这个参数能拿到…

Unity3d 灯光阴影开启,法线贴图出现BUG

URP项目打开灯光的阴影后&#xff0c;法线贴图出现BUG 解决方案&#xff1a;按照下图所示调整材质的选项即可

Vue3框架中让table合计居中对齐

第一步&#xff1a;给它加一个类名 center-table 如下&#xff1a; <el-table:data"datas.shows"max-height"600px"show-summarystripeborderstyle"width: 100%":header-cell-style"{ textAlign: center }":cell-style"{ text…

二叉树OJ题之二

今天我们一起来看一道判断一棵树是否为对称二叉树的题&#xff0c;力扣101题&#xff0c; https://leetcode.cn/problems/symmetric-tree/ 我们首先先来分析这道题&#xff0c;要判断这道题是否对称&#xff0c;我们首先需要判断的是这颗树根节点的左右子树是否对称&#xff0…

靡靡之音 天籁之声 ——Adobe Audition

上一期讲到了和Pr配合使用的字幕插件Arctime Pro的相关介绍。相信还记得的小伙伴应该记得我还提到过一个软件叫做Au。 当人们对字幕需求的逐渐满足&#xff0c;我们便开始追求更高层次的享受&#xff0c;当视觉享受在进步&#xff0c;听觉享受想必也不能被落下&#xff01; Au即…

HarmonyOS-Service服务开发(一)

文章目录 创建新项目启动Serviceets获取service的bundleName DataAbility开发指导开发Data步骤创建Data 创建新项目 ServiceAbility开发指导 在config.json中也有配置出现 启动Service ets获取service的bundleName 项目的bundleName service的bundleName 这里serviceAbil…

【同一局域网下】两台电脑之间互ping

两台电脑互ping 首先需要连接同一网咯关闭需要ping的电脑的防火墙 关闭防火墙步骤&#xff08;以win11系统为例&#xff09;&#xff1a; 设置 --> 隐私和安全性 --> Windows 安全中心 打开Windows安全中心 防火墙和网络保护 --> 选择正在使用的网络 关闭 ping其他…

自定义注解的定义及使用场景

文章目录 1. 自定义注解如何使用2. 自定义注解使用场景2.1 自定义注解使用AOP做权限校验2.2 自定义注解使用AOP记录用户操作日志2.3 自定义注解使用AOP记录接口请求时长 1. 自定义注解如何使用 需要使用interface修饰&#xff0c;加上三个元注解 Documented&#xff1a;生成API…

面试题:说一下MyBatis动态代理原理?

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.MyBatis简介2.使用步骤2.1、引入依赖2.2、配置文件2.3、接口定义2.4、加载执行 3.原理解析 1.MyBatis简介 MyBatis是一个ORM工具&#xff0c;封装了JDBC的操作&a…

Golang中rune和Byte,字符和字符串有什么不一样

Rune和Byte&#xff0c;字符和字符串有什么不一样 String Go语言中&#xff0c; string 就是只读的采用 utf8 编码的字节切片(slice) 因此用 len 函数获取到的长度并不是字符个数&#xff0c;而是字节个数。 for循环遍历输出的也是各个字节。 Rune rune 是 int32 …

在微服务架构中的数据一致性

当从传统的单体应用架构转移到微服务架构时&#xff0c;特别是涉及数据一致性时&#xff0c;数据一致性是微服务架构中最困难的部分。传统的单体应用中&#xff0c;一个共享的关系型数据库负责处理数据一致性。在微服务架构中&#xff0c;如果使用“每个服务一个数据库”的模式…

Ros报错:The Plugin for class ‘jsk_rviz_plugin/Plotter2D‘ failed to load

一般出现这种情况&#xff0c;是提醒Ros缺少某种库&#xff1a; 图中显示的错误是说明少了jsk_rviz_plugins库&#xff0c;他是一个提供原始rviz插件的包。 解决办法是安装相应的库与插件&#xff1a; #根据自己ROS的版本选择相应的指令 # ubuntu20.04:noetic sudo apt-get i…

幼教智能时代精英论坛北京举行

中国日报11月29日电 近日&#xff0c;智能时代赢之道—2023幼教智能时代精英论坛在北京泰山饭店成功举办&#xff0c;来自全国各地的近百位幼儿园园长、幼教老师集聚一堂&#xff0c;探索智能时代幼教的智慧化解决方案。 伴随“教育数字化战略行动”的深入开展&#xff0c;智慧…

Linux高级IO

文章目录 一.IO的基本概念二.钓鱼五人组三.五种IO模型四.高级IO重要概念1.同步通信 VS 异步通信2.阻塞 VS 非阻塞 五.其他高级IO六.阻塞IO七.非阻塞IO 一.IO的基本概念 什么是IO&#xff1f; I/O&#xff08;input/output&#xff09;也就是输入和输出&#xff0c;在著名的冯诺…

python爱心代码高级

在Python中&#xff0c;我们可以使用matplotlib库来创建一个更高级的爱心图形。以下是一个示例&#xff1a; import matplotlib.pyplot as pltimport numpy as npx np.linspace(-2, 2, 1000)y1 np.sqrt(1-(abs(x)-1)**2)y2 -3*np.sqrt(1-(abs(x)/2)**0.5)fig, ax plt.subp…