Flutter 异步编程简述

news2025/4/6 20:57:31

1、isolate 机制

1.1 基本使用

Dart 是基于单线程模型的语言。但是在开发当中我们经常会进行耗时操作比如网络请求,这种耗时操作会堵塞我们的代码。因此 Dart 也有并发机制 —— isolate。APP 的启动入口main函数就是一个类似 Android 主线程的一个主 isolate。与 Java 的 Thread 不同的是,Dart 中的 isolate 无法共享内存,因此也有称 isolate 是像进程一样的线程。

在 Dart 中,Isolate(隔离区)是一种独立运行的执行单元,它是 Dart 并发模型的基本组成部分。每个 Isolate 都有自己的内存堆,独立于其他 Isolate,并且彼此之间不共享内存。

Isolate 可以并行执行代码,使多个任务可以同时运行而互不干扰。每个 Isolate 都有自己的事件循环(event loop),可以独立地处理事件和执行任务。

通过使用 Isolate,您可以将应用程序的工作负载分发到多个 Isolate 中,从而实现并发处理和利用多核处理器的能力。

每个 isolate 都有一个 ReceivePort,ReceivePort 内又有一个 SendPort,可以将 SendPort 发送给对端的 isolate,这样就能实现两个 isolate 彼此通信了:

isolate

示例代码:

import 'dart:io';
import 'dart:isolate';

int i = 0;

void main() {
  i = 10;

  // 主 isolate 的 ReceivePort
  var receivePort = ReceivePort();
  // 创建子 isolate,传入主 isolate 的 SendPort
  Isolate.spawn(isolateSub, receivePort.sendPort);

  // 接收其他 isolate 发来的消息
  receivePort.listen((message) {
    // 如果对端发过来 sendPort,则主 isolate 也可以向对端的 isolate 发送消息
    if (message is SendPort) {
      message.send("主 isolate 接收到 SendPort");
    } else {
      print("接到子 isolate 消息:$message\n");
    }
  });

  // 休眠 5s 测试
  sleep(Duration(seconds: 5));
}

/// 新 isolate 的入口函数
void isolateSub(SendPort sendPort) {
  // isolate 是内存隔离的,i 的值是在子 isolate 中没有修改,因此为 0
  print(i);

  // 创建子 isolate 的 SendPort 并发给主 isolate
  var receivePort = ReceivePort();
  sendPort.send(receivePort.sendPort);

  // 向主 isolate 发送消息
  sendPort.send("子 isolate 发送的消息~");

  // 监听其他 isolate 发来的消息
  receivePort.listen((message) {
    print("接到主isolate消息:$message\n");
  });
}

这段代码要注意两个问题:

  1. ReceivePort 在使用完毕后要通过 close() 关闭掉
  2. 在 main 中给 receivePort 设置完监听之后 sleep 了 5 秒,目的是测试消息的收发情况,结果是先输出了 isolateSub() 打印的 i 的 0 值,然后隔了 5 秒,主 isolate 和子 isolate 才陆续输出接收到的消息。说明 Dart 真的是单线程

可以将 isolate 看成 Java 线程,只不过线程空间不共享。

1.2 Event-Loop

上面提到,如果在 main() 中休眠 5 秒,那么主与子 isolate 接收消息也会延后 5 秒。这是因为 Dart 与 Android 一样都是事件驱动的,通过 Event-Loop 不停的从队列中获取消息或者事件来驱动整个应用的运行。isolate 发过来的消息就是通过 Event-Loop 处理。但是与 Android 不同的是,Android 中每个线程只有一个 Looper 所对应的 MessageQueue,而 Dart 中有两个队列,一个叫做 Event queue(事件队列),另一个叫做 Microtask queue(微任务队列)

消息机制

Dart 在执行完 main 函数后,就会由 Loop 开始执行两个任务队列中的 Event:

  • 首先 Loop 检查微服务队列,依次执行 Event
  • 当微服务队列执行完后,就检查 Event queue 队列依次执行,在执行 Event queue 的过程中,每执行完一个 Event 就再检查一次微服务队列。所以微服务队列优先级高,可以利用微服务进行插队

我们来看几个例子:

import 'dart:io';

void main() {
  new File("/Users/enjoy/a.txt").readAsString().then((content) {
    print(content);
  });
  while (true) {}
}

文件内容永远也无法打印出来,因为 main 函数还没执行完。而 then 方法是由 Loop 检查 Event queue 执行的。

void main() {
  var receivePort = ReceivePort();
  receivePort.listen((message) {
    print(message);
  });

  receivePort.sendPort.send("发送消息1");
  Future.microtask(() => print("执行微任务1"));

  receivePort.sendPort.send("发送消息2");
  Future.microtask(() => print("执行微任务2"));
  
  receivePort.sendPort.send("发送消息3");
  Future.microtask(() => print("执行微任务3"));
}

输出的结果是:

执行微任务1
执行微任务2
执行微任务3
发送消息1
发送消息2
发送消息3

这是因为微服务队列优先级高,Loop 在 main() 执行完开始处理消息时,先去微服务队列,看到队列中有三个任务就都执行了,然后才去 Event queue 中执行任务,每执行完一个任务还要再去微服务队列中看一下是否有任务要插队进行,在这个例子中没有,所以才接连执行了“发送消息1”、“发送消息2”、“发送消息3”。

2、Future

Future 表示事件队列中一个事件的结果,通常异步函数返回的对象就是一个 Future。当一个 Future 执行完后,他里面的值就可以使用了,可以通过 then() 在 Future 完成时执行其他代码:

void main() {
  // readAsString() 返回 Future<String>
  File(r"D:\a1.txt").readAsString().then((value) => print(value));
}

2.1 异常处理

当给到一个不存在的文件地址时会发生异常,这时候可以利用 catchError 捕获此异常:

// then().catchError() 模式就是异步的 try-catch
void main() {
  File(r"D:\a2.txt")
      .readAsString()
      .then((value) => print(value))
      .catchError((e, s) {
    print(e);
  });
}

会打印输入如下信息:

PathNotFoundException: Cannot open file, path = 'D:\a2.txt' (OS Error: 系统找不到指定的文件。
, errno = 2)

2.2 组合

then()的返回值同样是一个 Future 对象,可以利用队列的原理进行组合异步任务:

void main() {
  File(r"D:\a1.txt").readAsString().then((value) {
    print(value);
    // 1 被转化为 Future<int> 类型返回
    return 1;
  }).then((value) => print(value));
}

上面是等待执行完成读取文件之后,再执行一个新的 Future。如果我们需要等待一组任务都执行完再统一处理一些事情,可以通过wait()完成:

var readFuture = File(r"D:\a1.txt").readAsString();
var delayedFuture = Future.delayed(const Duration(seconds: 3));

Future.wait([readFuture, delayedFuture]).then((value) {
    print(value[0]); // 第一个 Future 的结果,即文件中的字符串
    print(value[1]); // 第二个 Future 的结果,null
});

3、Stream

Stream,也就是流,表示发出的一系列的异步数据。Stream 是一个异步数据源,它是 Dart 中处理异步事件流的统一 API。

3.1 基本使用

Future 表示稍后获得的一个数据,所有异步的操作的返回值都用 Future 来表示。但是 Future 只能表示一次异步获得的数据。而 Stream 表示多次异步获得的数据。比如 IO 处理的时候,每次只会读取一部分数据和一次性读取整个文件的内容相比,Stream 的好处是处理过程中内存占用较小。而 File 的 readAsString()是一次性读取整个文件的内容进来,虽然获得完整内容处理起来比较方便,但是如果文件很大的话就会导致内存占用过大的问题。

  new File("/Users/enjoy/app-release.apk").openRead().listen((List<int> bytes) {
    print("stream执行"); // 执行多次
  });

  new File("/Users/enjoy/app-release.apk").readAsBytes().then((_){
    print("future执行"); // 执行1次
  });

以读取文件内容为例,如果文件太大不足以一次读取完,Stream 就会分多次读取,但是 Future 还是会一次读取完整个文件的内容。

Stream 的 listen() 其实就是订阅这个 Stream,它会返回一个 StreamSubscription,即订阅者。订阅者可以通过 cancel() 取消订阅,通过 onData() 重置 listen(),还有其他可调用方法如下所示:

var streamSubscription =
      File(r"D:\a1.txt").openRead().listen((List<int> bytes) {
    print("Stream 执行");
  });

// 重置 listen 方法
streamSubscription.onData((_) {
    print("替代 listen");
});

// 监听结束
streamSubscription.onDone(() {
    print("结束");
});

// 发生异常
streamSubscription.onError((e,s){
    print("异常");
});

// 暂停,如果没有继续则会退出程序
streamSubscription.pause();
// 恢复
streamSubscription.resume();
// 取消监听
streamSubscription.cancel();

3.2 广播模式

Stream 有单订阅和多订阅两种模式,默认是单订阅,可以通过 Stream.asBroadcastStream() 将单订阅变为多订阅:

  var stream = new File("/Users/enjoy/app-release.apk").openRead();
  stream.listen((List<int> bytes) {});
  // 错误 单订阅只能有一个订阅者
  //  stream.listen((_){
  //    print("stream执行");
  //  });

  var broadcastStream = new File("/Users/enjoy/app-release.apk").openRead().asBroadcastStream();
  broadcastStream.listen((_){
    print("订阅者1");
  });
  broadcastStream.listen((_){
    print("订阅者2");
  });

可以通过 isBroadcast 属性判断当前 Stream 所处的模式。

除了使用 Stream.asBroadcastStream() 将已经存在的 Stream 由单订阅变为多订阅之外,也可以使用 StreamController.broadcast() 直接创建一个多订阅的 Stream,只不过这样创建的 Stream,如果不及时添加订阅者可能会丢失数据:

void test() {
  // 1.由单订阅转换为多订阅的 Stream 具有粘性
  var stream = Stream.fromIterable([1, 2, 3]);
  Timer(Duration(seconds: 3), () => stream.listen(print));

  // 2.通过 StreamController 创建的 Stream 没有粘性
  var streamController = StreamController.broadcast();
  streamController.add(1);
  // 先发出事件再订阅,无法接到通知
  streamController.stream.listen((event) {
    print(event);
  });
  // 关闭
  streamController.close();
}

输出结果为 1 2 3,也就是在 stream 上先发送消息后订阅是可以收到消息的(由单订阅转为多订阅的 Stream 本质上还是单订阅的),但是对 streamController 的 stream 就不行。

4、async/await

当我们需要获得 A 的结果,再执行 B 时,你需要then()->then(),利用asyncawait能够非常好的解决回调地狱的问题。比如说,读取文件的操作一般要异步执行,但是读取多个文件时有先后顺序,那么就可以将读取操作先放入异步方法中,然后在方法内,对每个读取文件的操作都加上 await 变为同步操作:

/// async 表示这是一个异步方法,await 必须在 async方法中使用
/// 异步方法只能返回 void 或 Future
Future<String> readFile() async {
  // await 等待 Future 执行完成再执行后续代码,即阻塞
  String content = await File("/Users/a.txt").readAsString();
  String content2 = await File("/Users/b.txt").readAsString();
  // 自动转换为 future
  return "$content$content2";
}

简言之,async 与 await 搭配使用可以将异步变为同步,简化操作(避免回调地狱)。

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

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

相关文章

RAID5原理简介和相关问题

1、RAID5工作原理 2、RAID5单块硬盘的数据连续吗&#xff1f; 3、RAID5单块硬盘存储的是原始数据&#xff0c;还是异或后的数据&#xff1f; 4、RAID5的分块大小 ‌RAID5的分块大小一般选择4KB到64KB之间较为合适‌。选择合适的分块大小主要取决于以下几个考量因素&#xff1…

四、使用langchain搭建RAG:金融问答机器人--构建web应用,问答链,带记忆功能

经过前面3节完成金融问答机器人基本流程&#xff0c;这章将使用Gradio构建web应用&#xff0c;同时加入memory令提示模板带有记忆的&#xff0c;使用LCEL构建问答链。 加载向量数据库 from langchain.vectorstores import Chroma from langchain_huggingface import HuggingF…

理解神经网络

神经网络是一种模拟人类大脑工作方式的计算模型&#xff0c;是深度学习和机器学习领域的基础。 基本原理 神经网络的基本原理是模拟人脑神经系统的功能&#xff0c;通过多个节点&#xff08;也叫神经元&#xff09;的连接和计算&#xff0c;实现非线性模型的组合和输出。每个…

Mac系统下 IDEA配置Maven本地仓库

1.为什么需要配置本地仓库&#xff1f; 在软件开发过程中&#xff0c;使用Maven工具进行依赖管理是常见的做法。Maven通过集中管理各种依赖库&#xff0c;能够帮助开发者在项目中轻松地引入所需的第三方库&#xff0c;并确保项目能够顺利构建和部署。然而&#xff0c;在使用Mav…

selenium学习笔记(一)

文章目录 前言一、selenium的简介java使用seleniumPython使用selenium常用的浏览器selenium的功能 二、chromeDriver的安装查看本机的chrome版本&#xff1f;匹配对应的chromedriver并下载在服务器上例如Centos如何安装Chrome 三、selenium内容详解chrome启动chrome启动参数元素…

MDS-NPV/NPIV

在存储区域网络&#xff08;SAN&#xff09;中&#xff0c;域ID&#xff08;Domain ID&#xff09;是一个用于区分不同存储区域的关键参数。域ID允许SAN环境中的不同部分独立操作&#xff0c;从而提高效率和安全性。以下是关于域ID的一些关键信息&#xff1a; 域ID的作用&…

一篇文章学会HTML

目录 页面结构 网页基本标签 图像标签 超链接标签 文本链接 图像链接 锚链接 功能链接 列表 有序列表 无序列表 自定义列表 表格 跨列/跨行 表头 媒体元素 视频 音频 网站的嵌套 表单 表单元素 文本框 单选框 多选框 按钮 下拉框 文本域和文件域 表…

畅捷通-条件竞争

反编译dll 逻辑上很清晰了。取得上传数据然后直接写入Templates目录里去&#xff0c;且写入路径直接拼接文件名&#xff0c;说明写入路径可控。然后马上又调用Delete方法删除文件。看起来貌似很正常的样子&#xff0c;但实际上这里已经出现了严重的安全问题。首先是未限制上传…

web三、 window对象,延时器,定时器,时间戳,location对象(地址),本地存储-localStorage,数组去重new Set

一、window对象 window对象 是一个全局对象&#xff0c;也可以说是JavaScript中的 顶级对象 像document、alert()、console.log()这些都是window的属性&#xff0c;基本BOM的属性和方法都是window的 所有通过 var定义 在全局作用域中的 变量 、 函数 都会变成window对象的属…

VBA技术资料MF243:利用第三方软件复制PDF数据到EXCEL

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

RK356x-11:在win11的WSL中开发SDK

我拿到的SDK建议开发的系统是Ubuntu22.04&#xff08;在SDK-docs中的文档有说明&#xff09;&#xff0c;因此&#xff0c;WSL中要先安装好它。由于PC机上WSL本身不支持aarch64架构&#xff0c;所以&#xff0c;进行ARM64的RK356X开发&#xff0c;需要手动加入支持。用到的支持…

Java日志框架:log4j、log4j2、logback

文章目录 配置文件相关1. properties测试 2. XMl使用Dom4j解析XML Log4j与Log4j2日志门面 一、Log4j1.1 Logges1.2 Appenders1.3 Layouts1.4 使用1.5 配置文件详解1.5.1 配置根目录1.5.2 配置日志信息输出目的地Appender1.5.3 输出格式设置 二、Log4j22.1 XML配置文件解析2.2 使…

C语言(一)——初识C语言

目录 简单认识一段代码 数据类型 变量和常量 变量的作用域和变量的生命周期 常量 字符串 转义字符 注释 函数 数组 操作符 关键字 结构体 结构的声明 结构成员的类型 结构体变量的初始化 结构体传参 简单认识一段代码 main()函数是程序的入口&#xff0c;所以…

创新领先+效率领先,助力中国九牧加速品牌全球化

2024年&#xff0c;在全球市场经济和国家政策的双重驱动下&#xff0c;中国企业正在加速出海。 从早期粗放式的贴牌代工&#xff0c;到凝聚技术、产品、营销力的自主品牌出海&#xff0c;中国企业在国内市场对国际品牌上演过的追赶-超越戏码&#xff0c;如今正在海外市场上演。…

基于单片机的噪音检测系统(论文+源码)

1整体方案设计 2.2.1功能设计 本课题为噪音分贝仪&#xff0c;在功能上设计如下&#xff1a; 1.可以准确的识别周围环境的噪声大小。 2.检测的噪声大小可以通过液晶进行显示&#xff0c;并直观的给出当前噪声的程度大小&#xff1b; 3.可以通过按键设定报警阈值&#xff0…

十四、从0开始卷出一个新项目之瑞萨RZN2L之栈回溯(Default_Handler/hartfault)

目录 一、概述 二、参考资料 三、代码 四、日志 五、定位函数调用 六、README和工具 一、概述 软件开发中常见的比较棘手的问题就是hartfault/Default_Handler/dump&#xff0c;俗称跑飞了。 参考cmbacktrace&#xff0c;在瑞萨RZN2L/T2M实现栈回溯&#xff0c;串口打印…

OpenAI推出“深思熟虑对齐(Deliberative Alignment)”:为大语言模型建立更可靠的安全与伦理框架

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

GitCode 光引计划投稿 | GoIoT:开源分布式物联网开发平台

GoIoT 是基于Gin 的开源分布式物联网&#xff08;IoT&#xff09;开发平台&#xff0c;用于快速开发&#xff0c;部署物联设备接入项目&#xff0c;是一套涵盖数据生产、数据使用和数据展示的解决方案。 GoIoT 开发平台&#xff0c;它是一个企业级物联网平台解决方案&#xff…

【鸿蒙(HarmonyOS)性能优化指南】启动分析工具Launch Profiler

Launch Profiler概述 DevEco Studio内置Profiler分析调优工具。其中Launch主要用于分析应用或服务的启动耗时&#xff0c;分析启动周期各阶段的耗时情况、核心线程的运行情况等&#xff0c;协助开发者识别启动缓慢的原因。此外&#xff0c;Launch任务窗口还集成了Time、CPU、F…

微博用户消费趋势报告,多个领域增速明显,年轻一代成消费主力军

文 | 魏力 发布 | 大力财经 站在岁末回首这一年&#xff0c;在信息浪潮的汹涌翻涌之下&#xff0c;社交媒体平台犹如社会经济的晴雨表&#xff0c;精准地折射出大众生活与消费的万千景象。近日&#xff0c;大力财经看到一份报告&#xff0c;微博发布了《2024微博用户消费趋势…