注:如果async函数没有明确指定返回值,返回的null值的Future比如函数是打印 而不是return;
async 不是并行执行,它是遵循Dart 事件循环规则来执行的,它仅仅是一个语法糖,简化Future API的使用。
Future中的then并没有创建新的Event丢到Event Queue中,而只是一个普通的Function Call,在FutureTask执行完后,立即开始执行
当Future在then函数先已经执行完成了,则会创建一个task,将该task的添加到microtask queue中,并且该任务将会执行通过then传入的函数
Future只是创建了一个Event,将Event插入到了Event Queue的队尾
使用Future.value构造函数的时候,就会和第二条一样,创建Task丢到microtask Queue中执行then传入的函数
Future.sync构造函数执行了它传入的函数之后,也会立即创建Task丢到microtask Queue中执行
**Dart是单线程模型,什么是单线程模型呢?**单线程就是在程序执行时,所走的程序路径按照连续顺序排列下来,前面的必须处理好,后面的才会执行(就是同一个时刻只能执行一个操作)。
Dart是单线程模型,并没有主线程/子线程之分,Dart是Event loops和Event Queue模型,而EventLooper将所有的事件依次执行。
当产生一个Event之后,会进入Event queue,而Event loop从EventQueue中获取Event并且处理。
当一个Dart函数开始执行,那么它就会执行到这个函数结束,也就是函数不会被其他代码所打断。这里首先解释一下什么是Dart中的isolate(协程)。isolate本身是隔离的意思,有自己的内存和单线程控制的实体,因为isolate之间的内存在逻辑是隔离的,isolate的代码是按顺序执行的。在Dart中并发可以使用用isolate,isolate和Thread很像,但是isolate之间没有共享内存。一个Dart程序是在Main isolate的Main函数开始,我们平时开发中,默认环境就是Main isolate,App的启动入口main函数就是一个isolate,在Main函数结束后,Main isolate线程开始一个一个处理Event Queue中的每一个Event。
我们很容易发现,这种基于事件的异步模型,只适合I/O密集型的耗时操作,因为I/O耗时操作,往往是把时间浪费在等待对方传送数据或者返回结果,因此这种异步模型往往用于网络服务器并发。如果是计算密集型的操作,则应当尽可能利用处理器的多核,实现并行计算。
一个DartMain isolate只有一个消息循环(Event Looper)和两个消息队列:Event队列和MicroTask队列。
Event队列包含Dart和系统中其他位置的事件,MicroTask只包含Dart的代码,那么Event Looper处理两个队列的顺序是: 当main方法退出后,Event Looper就开始它的工作,首先会以FIFO的顺序执行MicroTask(先执行简短的异步任务),当所有的microtask执行完就会从Event队列去提取事件执行,这样反复,直到两个队列都是空。
这里要注意:**虽然可以预测知道任务执行顺序,但是无法准确预测事件循环什么时候处理期望的任务。**就好像创建了一个延时2s的任务,但是排在你之前的任务结束前事件处理是不会处理这个延时2s任务,也就是执行这个延时任务有可能大于2s。
new Future(() => futureTask) //异步任务的函数
.then((d) => "execute value:$d") //任务执行完后的子任务
.then((d) => d.length) //其中d为上个任务执行完后的返回的结果
.then((d) => printLength(d))
.whenComplete(() => whenTaskCompelete); //当所有任务完成后的回调函数
}
Dart 是事件驱动的体系结构,该结构基于具有单个事件循环和两个队列的单线程执行模型。
Dart虽然提供调用堆栈。 但是它使用事件在生产者和消费者之间传输上下文。 事件循环由单个线程支持,因此根本不需要同步和锁定。
Dart 的两个队列分别是
MicroTask queue 微任务队列
Event queue 事件队列
Dart中的Main Isolate只有一个Event Looper,但是存在两个Event Queue:Event Queue以及Microtask Queue
1. 先查看MicroTask队列是否为空,不是则先执行MicroTask队列
2. 一个MicroTask执行完后,检查有没有下一个MicroTask,直到MicroTask队列为空,才去执行Event队列
3. 在Evnet 队列取出一个事件处理完后,再次返回第一步,去检查MicroTask队列是否为空
注意:我们可以看出,将任务加入到MicroTask中可以被尽快执行,但也需要注意,当事件循环在处理MicroTask队列时,Event队列会被卡住,应用程序无法处理鼠标单击、I/O消息等等事件。
Future类是对未来结果的一个代理,它返回的并不是被调用的任务的返回值。
//我的任务
void myTask(){
print("this is my task");
}
void main() {
Future fut = new Future(myTask);//根据我的任务创建Future对象
}
如上代码,Future类实例fut并不是函数myTask的返回值,它只是代理了myTask函数,封装了该任务的执行状态。
什么是Future
Future表示在将来某时获取一个值的方式。当一个返回Future的函数被调用的时候,做了两件事情:
async和await关键字是Dart异步支持的一部分。他们允许你像写同步代码一样写异步代码和不需要使用Future接口。
注:在Dart2中有轻微的改动。async函数执行时,不是立即挂起,而是要执行到函数内部的第一个await。在多数情况下,我们不会注意到这一改动
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
printDailyNewsDigest() async {
String news = await gatherNewsReports();
print(news);
}
main() {
printDailyNewsDigest();
printWinningLotteryNumbers();
printWeatherForecast();
printBaseballScore();
}
printWinningLotteryNumbers() {
print('Winning lotto numbers: [23, 63, 87, 26, 2]');
}
printWeatherForecast() {
print("Tomorrow's forecast: 70F, sunny.");
}
printBaseballScore() {
print('Baseball score: Red Sox 10, Yankees 0');
}
const news = '' ;
Duration oneSecond = const Duration(seconds: 1);
final newsStream = new Stream.periodic(oneSecond, (_) => news);
// Imagine that this function is more complex and slow. :)
Future gatherNewsReports() => newsStream.first;
// Alternatively, you can get news from a server using features
// from either dart:io or dart:html. For example:
//
// import 'dart:html';
//
// Future gatherNewsReportsFromServer() => HttpRequest.getString(
// 'https://www.dartlang.org/f/dailyNewsDigest.txt',
// );
从执行结果我们可以注意到printDailyNewsDigest是第一个调用的,但是新闻是最后才打印,即使只要一行内容。这是因为代码读取和打印内容是异步执行的。
在这个例子中间,printDailyNewsDigest调用的gatherNewsReports不是阻塞的。gatherNewsReports把自己放入队列,不会暂停代码的执行。程序打印中奖号码,天气预报和比赛分数;当gatherNewsReports完成收集新闻过后打印。gatherNewsReports需要消耗一定时间的执行完成,而不会影响功能:用户在读取新闻之前获得其他消息。
重中之重
注:如果async函数没有明确指定返回值,返回的null值的Future
如果在Future返回函数发生错误,你可能想捕获错误。Async函数可以用try-catch捕获错误。
printDailyNewsDigest() async {
try {
String news = await gatherNewsReports();
print(news);
} catch (e) {
// Handle error...
}
}
ry-catch在同步代码和异步代码的表现是一样的:如果在try块中间发生异常,那么在catch中的代码执行
连续执行
你可以使用多个await表达式,保证一个await执行完成过后再执行下一个
// Sequential processing using async and await.
main() async {
await expensiveA();
await expensiveB();
doSomethingWith(await expensiveC());
}
expensiveB函数将等待expensiveA完成之后再执行。
import 'dart:async';
void main() {
print("main start");
Future fut =new Future.value(18);
// 使用then注册回调
fut.then((res){
print(res);
});
// 链式调用,可以跟多个then,注册多个回调
new Future((){
print("async task");
}).then((res){
print("async task complete");
}).then((res){
print("async task after");
});
print("main stop");
}
除了then方法,还可以使用catchError来处理异常,如下
new Future((){
print("async task");
}).then((res){
print("async task complete");
}).catchError((e){
print(e);
});
还可以使用静态方法wait 等待多个任务全部完成后回调。
import 'dart:async';
void main() {
print("main start");
Future task1 = new Future((){
print("task 1");
return 1;
});
Future task2 = new Future((){
print("task 2");
return 2;
});
Future task3 = new Future((){
print("task 3");
return 3;
});
Future fut = Future.wait([task1, task2, task3]);
fut.then((responses){
print(responses);
});
print("main stop");
}
输出为:
main start
main stop
task 1
task 2
task 3
[1, 2, 3]
前面已经说过,将非常耗时的任务添加到事件队列后,仍然会拖慢整个事件循环的处理,甚至是阻塞。可见基于事件循环的异步模型仍然是有很大缺点的,这时候我们就需要Isolate,这个单词的中文意思是隔离。
简单说,可以把它理解为Dart中的线程。但它又不同于线程,更恰当的说应该是微线程,或者说是协程。它与线程最大的区别就是不能共享内存,因此也不存在锁竞争问题,两个Isolate完全是两条独立的执行线,且每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程。
**isolate本身是隔离的意思,有自己的内存和单线程控制的实体,因为isolate之间的内存在逻辑是隔离的,isolate的代码是按顺序执行的。在Dart中并发可以使用用isolate,**isolate和Thread很像,但是isolate之间没有共享内存。一个Dart程序是在Main isolate的Main函数开始,我们平时开发中,默认环境就是Main isolate,App的启动入口main函数就是一个isolate,在Main函数结束后,Main isolate线程开始一个一个处理Event Queue中的每一个Event。
整个消息通信过程:
两个Isolate是通过两对Port对象通信,一对Port分别由用于接收消息的ReceivePort对象,和用于发送消息的SendPort对象构成。
其中SendPort对象不用单独创建,它已经包含在ReceivePort对象之中。
需要注意,一对Port对象只能单向发消息,这就如同一根自来水管,ReceivePort和SendPort分别位于水管的两头,水流只能从SendPort这头流向ReceivePort这头。因此,两个Isolate之间的消息通信肯定是需要两根这样的水管的,这就需要两对Port对象。
a、spawnUri用法
spawnUri方法有三个必须的参数,
需要注意,用于运行新Isolate的代码文件中,必须包含一个main函数,它是新Isolate的入口方法,**该main函数中的args参数列表,正对应spawnUri中的第二个参数。**如不需要向新Isolate中传参数,该参数可传空List
主Isolate中的代码:
import 'dart:isolate';
void main() {
print("main isolate start");
create_isolate();
print("main isolate stop");
}
// 创建一个新的 isolate
create_isolate() async{
ReceivePort rp = new ReceivePort();
SendPort port1 = rp.sendPort;
Isolate newIsolate = await Isolate.spawnUri(new Uri(path: "./other_task.dart"), ["hello, isolate", "this is args"], port1);
SendPort port2;
rp.listen((message){
print("main isolate message: $message");
if (message[0] == 0){
port2 = message[1];
}else{
port2?.send([1,"这条信息是 main isolate 发送的"]);
}
});
// 可以在适当的时候,调用以下方法杀死创建的 isolate
// newIsolate.kill(priority: Isolate.immediate);
}
创建other_task.dart文件,编写新Isolate的代码
import 'dart:isolate';
import 'dart:io';
void main(args, SendPort port1) {
print("isolate_1 start");
print("isolate_1 args: $args");
ReceivePort receivePort = new ReceivePort();
SendPort port2 = receivePort.sendPort;
receivePort.listen((message){
print("isolate_1 message: $message");
});
// 将当前 isolate 中创建的SendPort发送到主 isolate中用于通信
port1.send([0, port2]);
// 模拟耗时5秒
sleep(Duration(seconds:5));
port1.send([1, "isolate_1 任务完成"]);
print("isolate_1 stop");
}
运行主Isolate结果
main isolate start
main isolate stop
isolate_1 start
isolate_1 args: [hello, isolate, this is args]
main isolate message: [0, SendPort]
isolate_1 stop
main isolate message: [1, isolate_1 任务完成]
isolate_1 message: [1, 这条信息是 main isolate 发送的]
b、spawn用法
spawn更常用:
我们通常希望将新创建的Isolate代码和main Isolate代码写在同一个文件,且不希望出现两个main函数
而是将指定的耗时函数运行在新的Isolate,这样做有利于代码的组织和代码的复用
spawn方法有两个必须的参数,第一个是需要运行在新Isolate的耗时函数,第二个是动态消息,该参数通常用于传送主Isolate的SendPort对象。
spawn的用法:
import 'dart:isolate';
import 'dart:io';
void main() {
print("main isolate start");
create_isolate();
print("main isolate end");
}
// 创建一个新的 isolate
create_isolate() async{
ReceivePort rp = new ReceivePort();
SendPort port1 = rp.sendPort;
Isolate newIsolate = await Isolate.spawn(doWork, port1);
SendPort port2;
rp.listen((message){
print("main isolate message: $message");
if (message[0] == 0){
port2 = message[1];
}else{
port2?.send([1,"这条信息是 main isolate 发送的"]);
}
});
}
// 处理耗时任务
void doWork(SendPort port1){
print("new isolate start");
ReceivePort rp2 = new ReceivePort();
SendPort port2 = rp2.sendPort;
rp2.listen((message){
print("doWork message: $message");
});
// 将新isolate中创建的SendPort发送到主isolate中用于通信
port1.send([0, port2]);
// 模拟耗时5秒
sleep(Duration(seconds:5));
port1.send([1, "doWork 任务完成"]);
print("new isolate end");
}
运行结果
main isolate start
main isolate end
new isolate start
main isolate message: [0, SendPort]
new isolate end
main isolate message: [1, doWork 任务完成]
doWork message: [1, 这条信息是 main isolate 发送的]
无论是上面的spawn还是spawnUri,运行后都会创建Isolate,一个是主Isolate,一个是新Isolate,两个都双向绑定了消息通信的通道,即使新的Isolate中的任务完成了,它也不会立刻退出,因此,当使用完自己创建的Isolate后,最好调用newIsolate.kill(priority: Isolate.immediate);将Isolate立即杀死。
在Dart中创建一个Isolate都显得有些繁琐,Dart官方并未提供更高级封装
但是,如果想在Flutter中创建Isolate,则有更简便的API
compute函数
import 'package:flutter/foundation.dart';
import 'dart:io';
// 创建一个新的Isolate,在其中运行任务doWork
create_new_task() async{
var str = "New Task";
var result = await compute(doWork, str);
print(result);
}
void doWork(String value){
print("new isolate doWork start");
// 模拟耗时5秒
sleep(Duration(seconds:5));
print("new isolate doWork end");
return "complete:$value";
}
compute函数有两个必须的参数,
第一个是待执行的函数,这个函数必须是一个顶级函数,不能是类的实例方法,可以是类的静态方法,
第二个参数为动态的消息类型,可以是被运行函数的参数。
需要注意,使用compute应导入’package:flutter/foundation.dart’包。
Isolate虽好,但也有合适的使用场景,不建议滥用Isolate,应尽可能多的使用Dart中的事件循环机制去处理异步任务,这样才能更好的发挥Dart语言的优势。
那么应该在什么时候使用Future,什么时候使用Isolate呢?
一个最简单的判断方法是根据某些任务的平均时间来选择:
方法执行在几毫秒或十几毫秒左右的,应使用Future,如果一个任务需要几百毫秒或之上的,则建议创建单独的Isolate
除此之外,还有一些可以参考的场景
JSON 解码
加密
图像处理:比如剪裁
网络请求:加载资源、图片