编程中的代码执行,通常分为同步
与异步
两种。简单说,同步就是按照代码的编写顺序,从上到下依次执行,这也是最简单的我们最常接触的一种形式。但是同步代码的缺点也显而易见,如果其中某一行或几行代码非常耗时,那么就会阻塞,使得后面的代码不能被立刻执行。
异步的出现正是为了解决这种问题,它可以使某部分耗时代码不在当前这条执行线路上立刻执行,那究竟怎么执行呢?最常见的一种方案是使用多线程,也就相当于开辟另一条执行线,然后让耗时代码在另一条执行线上运行,这样两条执行线并列,耗时代码自然也就不能阻塞主执行线上的代码了。
多线程虽然好用,但是在大量并发时,仍然存在两个较大的缺陷,一个是开辟线程比较耗费资源,线程开多了机器吃不消,另一个则是线程的锁问题,多个线程操作共享内存时需要加锁,复杂情况下的锁竞争不仅会降低性能,还可能造成死锁。因此又出现了基于事件的异步模型。简单说就是在某个单线程中存在一个事件循环和一个事件队列,事件循环不断的从事件队列中取出事件来执行,这里的事件就好比是一段代码,每当遇到耗时的事件时,事件循环不会停下来等待结果,它会跳过耗时事件,继续执行其后的事件。当不耗时的事件都完成了,再来查看耗时事件的结果。因此,耗时事件不会阻塞整个事件循环,这让它后面的事件也会有机会得到执行。
我们很容易发现,这种基于事件的异步模型,只适合I/O
密集型的耗时操作,因为I/O
耗时操作,往往是把时间浪费在等待对方传送数据或者返回结果,因此这种异步模型往往用于网络服务器并发。如果是计算密集型的操作,则应当尽可能利用处理器的多核,实现并行计算。
在这里插入图片描述
Dart 是事件驱动的体系结构,该结构基于具有单个事件循环和两个队列的单线程执行模型。Dart虽然提供调用堆栈。但是它使用事件在生产者和消费者之间传输上下文。事件循环由单个线程支持,因此根本不需要同步和锁定。
Dart 的两个队列分别是
MicroTask queue
微任务队列Event queue
事件队列在这里插入图片描述
Dart事件循环执行如上图所示
MicroTask
队列是否为空,不是则先执行MicroTask
队列MicroTask
执行完后,检查有没有下一个MicroTask
,直到MicroTask
队列为空,才去执行Event
队列Evnet
队列取出一个事件处理完后,再次返回第一步,去检查MicroTask
队列是否为空我们可以看出,将任务加入到MicroTask
中可以被尽快执行,但也需要注意,当事件循环在处理MicroTask
队列时,Event
队列会被卡住,应用程序无法处理鼠标单击、I/O消息等等事件。
注意,以下调用的方法,都定义在dart:async
库中。
将任务添加到MicroTask
队列有两种方法
import 'dart:async';
void myTask(){
print("this is my task");
}
void main() {
// 1. 使用 scheduleMicrotask 方法添加
scheduleMicrotask(myTask);
// 2. 使用Future对象添加
new Future.microtask(myTask);
}
将任务添加到Event
队列
import 'dart:async';
void myTask(){
print("this is my task");
}
void main() {
new Future(myTask);
}
现在学会了调度任务,赶紧编写代码验证以上的结论
import 'dart:async';
void main() {
print("main start");
new Future((){
print("this is my task");
});
new Future.microtask((){
print("this is microtask");
});
print("main stop");
}
运行结果:
main start
main stop
this is microtask
this is my task
可以看到,代码的运行顺序并不是按照我们的编写顺序来的,将任务添加到队列并不等于立刻执行,它们是异步执行的,当前main
方法中的代码执行完之后,才会去执行队列中的任务,且MicroTask
队列运行在Event
队列之前。
如需要将任务延伸执行,则可使用Future.delayed
方法
new Future.delayed(new Duration(seconds:1),(){
print('task delayed');
});
表示在延迟时间到了之后将任务加入到Event
队列。需要注意的是,这并不是准确的,万一前面有很耗时的任务,那么你的延迟任务不一定能准时运行。
import 'dart:async';
import 'dart:io';
void main() {
print("main start");
new Future.delayed(new Duration(seconds:1),(){
print('task delayed');
});
new Future((){
// 模拟耗时5秒
sleep(Duration(seconds:5));
print("5s task");
});
print("main stop");
}
运行结果:
main start
main stop
5s task
task delayed
从结果可以看出,delayed
方法调用在前面,但是它显然并未直接将任务加入Event
队列,而是需要等待1秒之后才会去将任务加入,但在这1秒之间,后面的new Future
代码直接将一个耗时任务加入到了Event
队列,这就直接导致写在前面的delayed
任务在1秒后只能被加入到耗时任务之后,只有当前面耗时任务完成后,它才有机会得到执行。这种机制使得延迟任务变得不太可靠,你无法确定延迟任务到底在延迟多久之后被执行。
Future类是对未来结果的一个代理,它返回的并不是被调用的任务的返回值。
void myTask(){
print("this is my task");
}
void main() {
Future fut = new Future(myTask);
}
如上代码,Future
类实例fut
并不是函数myTask
的返回值,它只是代理了myTask
函数,封装了该任务的执行状态。
Future
的几种创建方法
Future()
Future.microtask()
Future.sync()
Future.value()
Future.delayed()
Future.error()
其中sync
是同步方法,任务会被立即执行
import 'dart:async';
void main() {
print("main start");
new Future.sync((){
print("sync task");
});
new Future((){
print("async task");
});
print("main stop");
}
运行结果:
main start
sync task
main stop
async task
当Future
中的任务完成后,我们往往需要一个回调,这个回调立即执行,不会被添加到事件队列。
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");
}
运行结果:
main start
main stop
18
async task
async task complete
async task after
除了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]
如上,wait
返回一个新的Future
,当添加的所有Future
完成时,在新的Future
注册的回调将被执行。
在Dart1.9中加入了async
和await
关键字,有了这两个关键字,我们可以更简洁的编写异步代码,而不需要调用Future
相关的API
将 async
关键字作为方法声明的后缀时,具有如下意义
Future
对象作为返回值// 导入io库,调用sleep函数
import 'dart:io';
// 模拟耗时操作,调用sleep函数睡眠2秒
doTask() async{
await sleep(const Duration(seconds:2));
return "Ok";
}
// 定义一个函数用于包装
test() async {
var r = await doTask();
print(r);
}
void main(){
print("main start");
test();
print("main end");
}
运行结果:
main start
main end
Ok
需要注意,async 不是并行执行,它是遵循Dart 事件循环规则来执行的,它仅仅是一个语法糖,简化Future API
的使用。