Flutter异步与线程详解
一:前言 - 关于多线程与异步
关于 Dart,我相信大家都知道Dart是一门单线程语言,这里说的单线程并不是说Dart没有或着不能使用多线程,而是Dart的所有API默认情况下都是单线程的。但大家也都知道Dart是有办法支持多线程和异步操作的,关于多线程和异步这两个概念是需要我们理清楚的,不能混淆它们的概念,给我们的理解造成困扰。
1、多线程是开辟另外一个线程来处理事件,每个线程都有单独的事件队列,互不影响,这个新线程和当前线程是并列执行的,有的共享数据空间有的不共享(比如Isolate)。
2、异步是不阻塞当前线程,将异步任务和当前线程的任务分开,异步任务后面的任务,不会等待异步任务执行完再执行,而是直接执行,与异步任务的回调没有关系,这样就不影响当前线程的执行,这就叫异步。
二:事件队列
这个和iOS比较类似,在Dart的线程中也存在事件循环和消息队列的概念,在Dart的线程中包含一个事件循环以及两个事件队列,我们先说清楚两个事件队列,再来整理它的事件循环或着说是消息循环机制是什么样子的。
1、事件任务队列(Event Queue):负责处理I/O事件、绘制事件、手势事件、接收其他Isolate消息等外部事件,Timer也是事件队列。
2、微任务队列(Microtask Queue)表示一个短时间内就会完成的异步任务。它的优先级最高,高于Event Queue,只要队列中还有任务,就可以一直霸占着事件循环。Microtask Queue添加的任务主要是由Dart内部产生,当然我们也可以自己添加任务到微任务队列中去,但是我们不要在Microtask Queue里面实现耗时操作避免阻塞Event Queue里的UI事件导致卡顿现象。因为微任务队列的优先级要比事件队列的高,所以事件循环每次循环总是先判断微任务队列中是否有任务需要执行,如果有则先执行微任务队列里的任务,执行完毕之后才会执行事件任务队列里的任务,就会造成卡顿。
关于事件循环的,需要我们特别留意的:
1、在Microtask不为空的时候,Run next Microtask 之后回到最开始,首先判断的是是否还存在微任务,有的话还是优先处理的。
2、在Event不为空的时候,Run next event之后,还是会回去判断是否有Microtask,这点就把前面优先级的问题说的很明白了,这两点需要我们特别留意,在下面我们说完这两个对垒任务的添加之后,我们会写一个稍微比较复杂的方法,仔细的分析一下上面这个事件循环机制。
三:异步
在异步调用中有三个关键词 【async】【await】【Future】,其中async和await/Future是一起使用的,在Dart中可以通过async和await进行一个异步操作,async表示开始一个异步操作,也可以返回一个Future结果。如果没有返回值,则默认返回一个返回值为null的Future,这点也比较容易理解,就像下面的方法,返回值是Future,而我们不写返回return也是可以编译过去的,就是它默认自己返回一个返回值为null的Future。
Future handleMessage(String message) async {
print(message);
}
Future:默认的Future是异步运行的,也就是把任务放在Future函数体中,这个函数题会被异步执行。
async:异步函数标识,一般与await和Future配合使用。
await:等待异步结果返回,一般加在Future函数体之前,表明后面的代码要等这个Future函数体内的内容执行完在执行,实现同步执行。单独给函数添加async
关键字, 没有意义,函数是否是异步的,主要看Future。
注意:Future<T>通过泛型指定类型的异步操作结果(不需要结果可以使用Future<void>)当一个返回Future对象的函数被调用时,函数将被放入队列等待执行并返回一个未完成的Future对象,等函数操作执行完成时,Future对象变为完成并携带一个值或一个错误。也就是说首先Future是个泛型类,可以指定类型。如果没有指定相应类型的话,则Future会在执行动态的推导类型。
class asyncIsolate { Future<HttpClientRequest> dataReqeust() async { var httpClient = new HttpClient();
/// var uri = Uri.https('example.org', '/path', {'q': 'dart'});
/// print(uri); // https://example.org/path?q=dart
Future<HttpClientRequest> request =
httpClient.getUrl(Uri.https('jsonplaceholder.typicode.com', '/posts'));
return request;
} Future<String> loadData() async { HttpClientRequest request = await dataReqeust();
var response = await request.close();
var responseBody = await response.transform(Utf8Decoder()).join();
print('请求到的数据为:\n $responseBody');
return responseBody;
} }
上面的方法是一个请求数据的小demo,我们调用loadData方法进行数据请求,在运行到loadData内部时候,执行到await会阻塞async内部的执行,从而继续执行外面的代码,一直到dataReqeust的方法有返回,再接着async内部的执行,所以需要知道的事await不会阻塞方法外部代码的执行。
Future可以看做是一个延迟操作的封装,可以将异步任务封装为Future对象。获取到Future对象后,最简单的方法就是用await修饰,并等待返回结果继续向下执行。在Dart
中,和时间相关的操作基本都和Future有关,例如延时操作、异步操作等,下面是一个最简单的延迟操作的例子:
/// 延迟操作
delayedWithFuture() { DateTime now = DateTime.now();
print("开始时间: $now");
Future.delayed(Duration(seconds: 10), () {
now = DateTime.now();
print("延迟10秒后的时间: $now");
}); /*
flutter: 开始时间: 2022-05-09 13:30:07.164114
flutter: 延迟10秒后的时间: 2022-05-09 13:30:17.171057
*/
}
Dart还支持对Future的链式调用,通过追加一个或多个then方法来实现,这个特性非常实用。例如一个延时操作完成后,会调用then方法,并且可以传递一个参数给then,比如下面的例子:
delayWithFutureThen() { Future.delayed(Duration(seconds: 5), () {
int age = 30;
return age;
}).then((onValue) {
onValue++;
print('我多大了啊 $onValue');
}); /*
flutter: 我多大了啊 31
*/
}
Future还有很多有意思的方法,比如 Future.doWhile() 、Future.any()、Future.wait(),我们简单的看一个,比如Future.wait的用法,假设我们有这个一个使用场景,等待在三个Future执行完之后我们还需要执行另外一个Future,这时候我们该怎么处理,下面的demo给了我们处理的方式,注意下输出的日志,我们第一个是延迟的Future,是延迟两秒后输出的。
///
void awaitWithFuture() async { Future future1 = Future.delayed(Duration(seconds: 2), () {
print(1);
return 1;
}); Future future2 = Future(() {
print(2);
return 2;
}); Future future3 = Future(() {
print(3);
return 3;
}); Future.wait([future1, future2, future3]).then((value) {
print(value);
}).catchError((error) {}); /*
flutter: 2
flutter: 3
flutter: 1
flutter: [1, 2, 3]
*/
}
微任务队列添加任务,我们通过scheduleMicrotask添加微任务,具体的我们就不在这再写了,的确添加比较简单,接下来我们写一个事件队列和微任务队列在一起的demo,我们梳理一下执行的过程,加深一下对事件循环的理解:
analyseWithAsyncTask() { print('foundation start');
Future.delayed(Duration(seconds: 2), (() {
print('Future - delayed 2 second'); // --- 6
})); Future(() {
print('Future - 1'); // --- 2
}); Future(() {
print('Future - 2'); // --- 3
}); Timer(Duration(seconds: 3), (() {
print('Timer - delayed 3 second'); // --- 7
})); scheduleMicrotask((() {
print("Microtask - 1"); // --- 1
Future(() {
print('Future - 3'); // --- 5 【4的Future添加的比Future - 3要早】
});
})); Future(() {
scheduleMicrotask((() {
print("Microtask - 2"); // --- 4
}));
}); print('foundation end');
} /*
按照自己理解写出来的执行顺序 先到foundation start 再执行到
Future.delayed(Duration(seconds: 2), (() {
print('Future - delayed 2 second'); // --- 6
}));
没有这个Future添加到事件队列、后面的
Future(() {
print('Future - 2'); // --- 3
}); Timer(Duration(seconds: 3), (() {
print('Timer - delayed 3 second'); // --- 7
}));
也一样,没有先添加到事件队列、接下来是
scheduleMicrotask((() {
print("Microtask - 1"); // --- 1
Future(() {
print('Future - 3'); // --- 5 【4的Future添加的比Future - 3要早】
});
}));
判断微任务队列没有这个任务,添加到微任务队列。再到后面的
Future(() {
scheduleMicrotask((() {
print("Microtask - 2"); // --- 4
}));
});
也一样,也是没有就添加到事件队列,接着就是先打印foundation end
接下来判断有没有优先级更搞得微任务队列是否为空,判断有任务不为空,则执行微任务输出 - Microtask - 1 ,继续执行判断没有事件任务Future - 3
把事件任务添加到事件队列,注意这个事件任务的位置是在标记了// --- 4的事件后面的,执行完判断有没有微任务,发现没有了,开始添加的顺序执行事件任务
就输出了Future - 1 Future - 2 ,执行// --- 4的时候发现微任务,添加到微任务队列,执行下一个事件任务之前,判断有没有微任务,有的话就去执行微任务
就执行了Microtask - 2 ,继续判断微任务空了,继续事件任务。就到了Future - 3 最后两个延时的,安演示正长短 短的先执行 foundation start
foundation end
Microtask - 1
Future - 1
Future - 2
Microtask - 2
Future - 3
Future - delayed 2 second
Timer - delayed 3 second 实际日志输出:
flutter: foundation start
flutter: foundation end
flutter: Microtask - 1
flutter: Future - 1
flutter: Future - 2
flutter: Microtask - 2
flutter: Future - 3
flutter: Future - delayed 2 second
flutter: Timer - delayed 3 second
*/
四: 多线程 - Isolate
late SendPort subSendPort; /// 创建新的线程
dispatchQueueAsyncThread() async {
/// 主线程端口
ReceivePort mainThreadPort = ReceivePort(); /// 创建一个新的线程
/*
external static Future<Isolate> spawn<T>(
void entryPoint(T message), T message,
{bool paused = false, | { }里面的这些参数是可选类型的
bool errorsAreFatal = true, |
SendPort? onExit, |
SendPort? onError, |
@Since("2.3") String? debugName}); |
*/ Isolate isolate =
await Isolate.spawn<SendPort>(dataLoader, mainThreadPort.sendPort);
mainThreadPort.listen((message) {
///
print(message); if (message is SendPort) {
subSendPort = message;
print("子线程创建成功");
print("主线程收到了子线程的ReceivePort的Sendport了,可以通信了");
} else if (message is String) {
if (message == "closed") {
/// 结束这个线程
print('isolate kill');
isolate.kill();
}
}
});
} /// 主线程发送消息给子线程
mainSendMessageToSubThread() {
if (subSendPort != null) {
subSendPort.send("我是你的主线程");
}
} /// 主线程发送关闭端口的消息给子线程
/// 子线程关闭接口端口 并告诉主线程 主线程结束子线程
mainSendClosedThreadMessageToSubThread() {
if (subSendPort != null) {
subSendPort.send("close receiveport");
}
} // 这个SendPort是前面主线程的
static dataLoader(SendPort sendPort) async {
/// 子线程的ReceivePort创建
ReceivePort subThreadPort = ReceivePort(); /// 这是把子线程的ReceivePort的sendPort给了主线程 用于通信
sendPort.send(subThreadPort.sendPort); subThreadPort.listen((message) {
print("子线程收到的消息 $message");
if (message is String) {
/// 收到主线程让关闭接口的消息 就关闭 正确的话后面是在接受不到消息了
if (message == "close receiveport") {
sendPort.send('closed');
subThreadPort.close();
}
}
});
} /*
flutter: SendPort
flutter: 子线程创建成功
flutter: 主线程收到了子线程的ReceivePort的Sendport了,可以通信了
flutter: 子线程收到的消息 我是你的主线程
flutter: 子线程收到的消息 close receiveport
flutter: closed
flutter: isolate kill
*/
Isolate的线程更加偏向于底层,在生成一个Isolate之后,其内存是各自独立的,相互之间并不能进行访问,在进行Isolate消息传递的过程中,本质上就是进行Port的传递,通过上面的小例子我们基本上也就掌握了最基础的Flutter消息线程创建和线程之间的消息传递。
Flutter异步与线程详解的更多相关文章
- php为什么需要异步编程?php异步编程的详解(附示例)
本篇文章给大家带来的内容是关于php为什么需要异步编程?php异步编程的详解(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 我对 php 异步的知识还比较混乱,写这篇是为了 ...
- Javascript 异步加载详解
Javascript 异步加载详解 本文总结一下浏览器在 javascript 的加载方式. 关键词:异步加载(async loading),延迟加载(lazy loading),延迟执行(lazy ...
- 通用线程:POSIX 线程详解,第 3 部分 条件互斥量(pthread_cond_t)
使用条件变量提高效率 本文是 POSIX 线程三部曲系列的最后一部分,Daniel 将详细讨论如何使用条件变量.条件变量是 POSIX 线程结构,可以让您在遇到某些条件时“唤醒”线程.可以将它们看作是 ...
- “全栈2019”Java多线程第二十五章:生产者与消费者线程详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Flutter完整开发实战详解
Flutter完整开发实战详解(一.Dart语言和Flutter基础) Flutter完整开发实战详解(二. 快速开发实战篇) Flutter完整开发实战详解(三. 打包与填坑篇)
- python线程详解
#线程状态 #线程同步(锁)#多线程的优势在于可以同时运行多个任务,至少感觉起来是这样,但是当线程需要共享数据时,可能存在数据不同步的问题. #threading模块#常用方法:'''threadin ...
- 通用线程:POSIX 线程详解,第 3 部分
通用线程:POSIX 线程详解,第 3 部分 使用条件变量提高效率 Daniel Robbins, 总裁兼 CEO, Gentoo Technologies, Inc. 简介: 本文是 POSIX 线 ...
- POSIX 线程详解(经典必看)
http://www.cnblogs.com/sunminmin/p/4479952.html 总共三部分: 第一部分:POSIX 线程详解 ...
- mysql后台线程详解
1.mysql后台线程 mysql后台线程主要用于维持服务器的正常运行和完成用户提交的任务,主要包括:master thread,read thread,write thread,redo log t ...
随机推荐
- 你将如何使用 thread dump?你将如何分析 Thread dump?
新建状态(New) 用 new 语句创建的线程处于新建状态,此时它和其他 Java 对象一样,仅仅在堆区 中被分配了内存. 就绪状态(Runnable) 当一个线程对象创建后,其他线程调用它的 sta ...
- 学习MFS(五)
########################################################## mfs master 安装 建议 cp eth0 eth0:0 ifup eth ...
- 03-三高-并行并发&服务集群
三高项目 服务并行&并发 并行和并发 服务的搭建中,并行 并发.----并发. 集群 同质的(同样的配置,运行同样的程序,对外提供同样的服务). 修改同样的存储,可以认. (小建议 ...
- CAN总线系列讲座第六讲——SJA1000的滤波器设置
CAN总线的滤波器设置就像给总线上的节点设置了一层过滤网,只有符合要求的CAN信息帧才可以通过,其余的一概滤除. 在验收滤波器的帮助下,只有当接收信息中的识别位和验收滤波器预定义的值相等时,CAN 控 ...
- 要多简单就有多简单的H5拍照加水印
来一个简单粗暴的gif演示图 先来html 内容 <video id="video" width="320" height="240" ...
- java中如何知道一个字符串中有多少个字,把每个字打印出来,举例
9.6 About string,"I am ateacher",这个字符串中有多少个字,且分别把每个字打印出来. public class Test { static i ...
- 校验ip地址的格式
/*输入:strIP:ip地址 返回:如果通过验证返回true,否则返回false: */ function isIP(strIP) { if (isNull(strIP)) return false ...
- JavaScript实现简单轮播图动画
运行效果: 源代码: <!DOCTYPE html> <html lang="zh"> <head> <meta charset=&quo ...
- CommonsCollection6反序列化链学习
CommonsCollection6 1.前置知识 1.1.HashSet HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合.继承了序列化和集合 构造函数参数为空的话创建一 ...
- SQL注入之延迟盲注
延迟盲注 你不知道你输入的数据在sql被执行后是否是正确或错误的.你只知道有数据. 利用原理 借助if语句,如果正确就sleep(5),延迟5秒返回数据.通过数据返回的时间大小判断自己的语句是否正确执 ...