Flutter 异步编程简述

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 的 ReceivePortvar receivePort = ReceivePort();// 创建子 isolate,传入主 isolate 的 SendPortIsolate.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 中没有修改,因此为 0print(i);// 创建子 isolate 的 SendPort 并发给主 isolatevar 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();// 自动转换为 futurereturn "$content$content2";
}

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

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

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

相关文章

一键打断线(根据相交点打断)——CAD c# 二次开发

多条相交线根据交点一键打断&#xff0c;如下图&#xff1a; 部分代码如下: finally namespace IFoxDemo; public class Class1 {[CommandMethod("ddx")]public static void Demo(){//"ifox可以了".Print();Database db HostApplicationServices.Workin…

Confluent Cloud Kafka 可观测性最佳实践

Confluent Cloud 介绍 Confluent Cloud 是一个完全托管的 Apache Kafka 服务&#xff0c;提供高可用性和可扩展性&#xff0c;旨在简化数据流处理和实时数据集成。用户可以轻松创建和管理 Kafka 集群&#xff0c;而无需担心基础设施的维护和管理。Confluent Cloud 支持多种数据…

【C++】B2066救援题目分析和解决讲解

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af; 题目&#x1f4af; 题目分析每个屋顶计算的元素 &#x1f4af; 思路解析1. **读取输入**2. **计算屋顶时间**3. **结果精确取整** &#x1f4af; 完整解决代码&#x1f4a…

WPS工具栏灰色怎么办

WPS离线不登录&#xff0c;开启工具栏等相关功能 当你在使用WPS的过程中&#xff0c;若因网络问题或其他特殊原因&#xff0c;导致无法登录使用WPS时&#xff0c;可根据以下步骤开启离线兼容模式&#xff0c;开启此模式后&#xff0c;可在未登录的状态下&#xff0c;激活并使用…

反射探针.

一、在unity场景中如何添加反射探针&#xff1f; 可以先添加一个空对象&#xff0c;在空对象的上方添加反射探针组件&#xff08;Reflection Probe&#xff09; 反射探针的类型有&#xff1a;Baked、Custom、Realtime 其中“Baked”反射探针类型&#xff0c;可以将场景中的静态…

SecureCRT汉化版

目录 9.5.1版 8.1.4版 下载链接 SecureCRT 和 SecureFX 是由 VanDyke Software 开发的专业工具&#xff0c;分别专注于安全的终端仿真与文件传输。SecureCRT 提供高效的终端仿真和多协议支持&#xff0c;是网络管理和系统配置的首选工具&#xff1b;SecureFX 则致力于安全的…

回归预测 | MATLAB实现CNN-LSSVM卷积神经网络结合最小二乘支持向量机多输入单输出回归预测

回归预测 | MATLAB实现CNN-LSSVM卷积神经网络结合最小二乘支持向量机多输入单输出回归预测 目录 回归预测 | MATLAB实现CNN-LSSVM卷积神经网络结合最小二乘支持向量机多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 回归预测 | MATLAB实现CNN-LSSVM…

使用Vue的props进行组件传递校验时出现 Extraneous non-props attributes的解决方案

作者&#xff1a;CSDN-PleaSure乐事 欢迎大家阅读我的博客 希望大家喜欢 使用环境&#xff1a;WebStorm 目录 出现错误的情况 报错&#xff1a; 代码&#xff1a; 报错截图 原因分析 解决方案 方法一 方法二 出现错误的情况 以下是我遇到该错误时遇到的报错和代码&…

【知识】cuda检测GPU是否支持P2P通信及一些注意事项

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 代码流程 先检查所有GPU之间是否支持P2P通信&#xff1b;然后尝试启用GPU之间的P2P通信&#xff1b;再次检查所有GPU之间是否支持P2P通信。 test.cu&…

专栏二十三:Python读取和分析空间数据的经验杂谈

部分情况同样适合单细胞的分析结果 读取数据阶段 1.错误的library_id 包括sc和sq的两种读取方式&#xff0c;大同小异。 理论上有h5数据和spatial文件夹就可以读取成功&#xff0c;并且自动赋予和文件名一样的library_id&#xff0c;例如 slide sq.read.visium("/ho…

《软件设计的哲学》阅读摘要之设计原则

《软件设计的哲学》&#xff08;A Philosophy of Software Design&#xff09;是一本在软件架构与设计领域颇具影响力的书籍&#xff0c;作者 John Ousterhout 在书中分享了诸多深刻且实用的软件设计理念。书中列举的这些设计原则&#xff0c;汇聚了作者丰富的实战经验与深邃的…

Centos7.9安装openldap+phpldapadmin+grafana配置LDAP登录最详细步骤 亲测100%能行

一、部署LDAP 1、安装LDAP yum install -y openldap-servers openldap-clients openldap openldap-devel compat-openldap openldap-servers-sql systemctl start slapd systemctl enable slapd2、创建第一个管理账号密码&#xff08;设置为ldapadmin&#xff09; slappass…

【MySQL基础篇】多表查询(隐式/显式内连接、左/右外连接、自连接查询、联合查询、标量/列/行/表子查询)

Hiヽ(゜▽゜ )&#xff0d;欢迎来到蓝染Aizen的CSDN博客~ &#x1f525; 博客主页&#xff1a; 【✨蓝染 の Blog&#x1f618;】 &#x1f496;感谢大家点赞&#x1f44d; 收藏⭐ 评论✍ 文章目录 MySQL基础篇-多表查询一、多表关系1. 一对多2. 多对多3. 一对一 二、多表查询…

【踩坑记录】C编程变量未初始化导致的程序异常

1、在编程的时候养成良好的习惯&#xff0c;定义变量以后记得给变量初始化&#xff0c;不然可能会产生一些意想不到的Bug。 2、比如下面的例子&#xff0c;如果定义的变量没有被初始化就有可能是一个随机值。如果代码少还好&#xff0c;很容易排查出来。但如果是一个比较大的项…

如何查看pad的console输出,以便我们更好的进行调试,查看并了解实际可能的问题。

1、以下是baidu AI回复&#xff1a; 2、说明&#xff1a; 1&#xff09;如果小伙伴们经常做android开发的话&#xff0c;这个不陌生&#xff0c;因为调试都是要开启这个开发者模式。并启用USB调试模式。 2&#xff09;需要连上USB线&#xff0c;有的时候会忘记&#xff0c;然…

c++ [spdlog 配置与使用]

一、 下载spdlog https://codeload.github.com/gabime/spdlog/zip/refs/heads/v1.x spdlog链接 二、配置工程编译&#xff0c;和eigen库类似spdlog无需单独编译 拷贝到工程目录下 配置目录 稍微封装一下符合qDebug() 使用习惯 /* ** File name: LogSystem.h ** Auth…

leetcode-80.删除有序数组的重复项II-day12

总结&#xff1a;不必过于死磕一道题目&#xff0c;二十分钟没做出来就可参考题解

ES已死,文本检索永生

长期以来&#xff0c;混合查询&#xff08;Hybrid Search&#xff09;一直是提升 RAG&#xff08;Retrieval-Augmented Generation&#xff09;搜索质量的重要手段。尽管基于密集向量&#xff08;Dense Embedding&#xff09;的搜索技术随着模型规模和预训练数据集的不断扩展&a…

【Web】2024“国城杯”网络安全挑战大赛决赛题解(全)

最近在忙联通的安全准入测试&#xff0c;很少有时间看CTF了&#xff0c;今晚抽点时间回顾下上周线下的题(期末还没开始复习&#x1f622;) 感觉做渗透测试一半的时间在和甲方掰扯&水垃圾洞&#xff0c;没啥惊喜感&#xff0c;还是CTF有意思 目录 Mountain ez_zhuawa 图…

VS2022 中的 /MT /MTd /MD /MDd 选项

我们有时编译时,需要配置这个 运行库,指定C/C++运行时库的链接方式。 如下图 那么这些选项的含义是什么? /MT:静态链接多线程库 /MT选项代表“Multi-threaded Static”,即多线程静态库。选择此选项时,编译器会从运行时库中选择多线程静态连接库来解释程序中的代码,…