异步
简介
对于异步不太了解,可以直接看官网介绍;
Dart 有一些语言特性来支持异步编程。最常见的特性是 async
方法和 await
表达式。
Dart 库中有很多返回 Future 或者 Stream 对象的方法。 这些方法是异步的 : 这些函数在设置完基本的操作后就返回了 , 而无需等待操作执行完成。 例如读取一个文件 , 在打开文件后就返回了。
async 和 await
有两种方式可以使用 Future 对象中的数据:
- 使用 async 和 await
- 使用 Future API
使用 async
和 await
的代码是异步的, 但是看起来有点像同步代码。 例如,下面是一些使用 await
来 等待异步方法返回的示例:
await lookUpVersion();
复制代码
要使用 await
,其方法必须带有 async
关键字:
checkVersion() saync {
var version = await lookUpVersion();
if(version == expectedVersion){
// TODO ------
} else {
// TODO ------
}
}
复制代码
可以使用 try
, catch
, 和 finally
来处理使用 await
的异常:
void getHttp(){
try{
var response = await Dio().get("http://www.baidu.com");
print(response);
}catch(e){
print(e);
}
}
复制代码
声明异步方法
-
一个 async 方法 是函数体被标记为 async 的方法。 虽然异步方法的执行可能需要一定时间,但是 异步方法立刻返回 - 在方法体还没执行之前就返回了。
void getHttp async { // TODO --- } 复制代码
在一个方法上添加 async 关键字,则这个方法返回值为 Future。 例如,下面是一个返回字符串的同步方法:
String loadAppVersion() => "1.0.2"
复制代码
如果使用 async 关键字,则该方法返回一个 Future,并且 认为该函数是一个耗时的操作。
Futre<String> loadAppVersion() async => "1.0.2"
复制代码
注意,方法的函数体并不需要使用 Future API。 Dart 会自动在需要的时候创建 Future 对象。
推荐使用 async / await 而不是直接使用底层的特性
- 好的习惯
void main() {
//调用异步方法
doAsync();
}
// 在函数上声明了 async 表明这是一个异步方法
Future<bool> doAsync() async {
try {
// 这里是一个模拟请求一个网络耗时操作
var result = await getHttp();
//请求出来的结果
return printResult(result);
} catch (e) {
print(e);
return false;
}
}
//将请求出来的结果打印出来
Future<bool> printResult(summary) {
print(summary);
}
//开始模拟网络请求 等待 5 秒返回一个字符串
getHttp() {
return new Future.delayed(Duration(seconds: 5), () => "Request Succeeded");
}
复制代码
- 坏的习惯写法
void main() {
doAsync();
}
Future<String> doAsync() async {
return getHttp().then((r){
return printResult(r);
}).catchError((e){
print(e);
});
}
Future<String> printResult(summary) {
print(summary);
}
Future<String> getHttp() {
return new Future.delayed(Duration(seconds: 5), () => "Request Succeeded");
}
复制代码
then,catchError,whenComplete
先来看一段代码
void doAsyncs() async{
//then catchError whenComplete
new Future(() => futureTask()) // 异步任务的函数
.then((m) => "1-:$m") // 任务执行完后的子任务
.then((m) => print('2-$m')) // 其中m为上个任务执行完后的返回的结果
.then((_) => new Future.error('3-:error'))
.then((m) => print('4-'))
.whenComplete(() => print('5-')) //不是最后执行whenComplete,通常放到最后回调
// .catchError((e) => print(e))//如果不写test默认实现一个返回true的test方法
.catchError((e) => print('6-catchError:' + e), test: (Object o) {
print('7-:' + o);
return true; //返回true,会被catchError捕获
// return false; //返回false,继续抛出错误,会被下一个catchError捕获
})
.then((_) => new Future.error('11-:error'))
.then((m) => print('10-'))
.catchError((e) => print('8-:' + e))
;
}
futureTask() {
// throw 'error';
return Future.delayed(Duration(seconds: 5),() => "9-走去跑步");
}
复制代码
大家猜一猜,看下是怎么样的一个执行流程;我直接放上答案吧,我们来分析下
2-1-:9-走去跑步
5-
7-:3-:error
6-catchError:3-:error
8-:11-:error
复制代码
当异步函数 futureTask() 执行完会在内存中保存 ‘9-走去跑步’ 然后继续执行下一步 这个时候遇见了 then 现在会在内存中保存 “1-: 9-走去跑步 ” 继续执行 这个时候遇见了打印输出 2-1-:9-走去跑步 。现在第一个打印出来了。接着执行下一个 then() 这个时候遇见了一个 error 异常,Dart 会把这个异常保存在内存直到遇见捕获异常的地方。下面执行 whenComplete 这个函数 打印 5- 。然后遇见了一个捕获异常的函数 catchError 如果 test 返回 true ,会被 catchError 捕获 打印 7-:3-:error 6-catchError:3-:error。如果返回 false 只打印 7-:3-:error,会把 error 抛给下一个 catchError 。继续执行 又遇见了一个 error 11-:error ,现在出现 error 了 所以 then 10- 就不会执行了 。最后就直接捕获异常 打印 "8-11-error"。
分析完了,大家会了吗?相信大家差不多会了!
Event-Looper
以下内容从官网所得到
Dart 是单线程模型 Main, 也就没有了所谓的主线程/子线程之分。
Dart 也是 Event-Loop 以及 Event - Queue 的模型,所有的事件都是通过 Event-loop 的依次执行。
而 Dart 的 Event Loop 就是:
-
从 EventQueue 中获取 Event
-
处理 Event
-
直到 EventQueue 为空
而这些 Event 包括了用户输入,点击, Timer, 文件 IO 等
单线程模型
一旦某个 Dart 的函数开始执行,它将执行到这个函数结束,也就是 Dart 的函数不会被其他 Dart 代码打断。
Dart 中没有线程的概念,只有 isolate(隔离),每个 isolate 都是隔离的,并不会共享内存。而一个 Dart 程序是在 Main isolate 的 main 函数开始,而在 Main 函数结束后,Main isolate 线程开始一个一个(one by one)的开始处理 Event Queue 中的每一个 Event 。
Event Queue 和 Microtask Queue
Dart 中的 Main Isolate 只有一个 Event - Looper,但是存在两个 Event Queue : Event Queue 以及 Microtask Queue Microtask Queue 存在的意义是: 希望通过这个 Queue 来处理稍晚一些的事情,但是在下一个消息到来之前需要处理完的事情。 当 Event Looper 正在处理 Microtask Queue 中的 Event 时候,Event Queue 中的 Event 就停止了处理了,此时 App 不能绘制任何图形,不能处理任何鼠标点击,不能处理文件 IO 等等。
Event-Looper 挑选 Task 的执行顺序为:
-
优先全部执行完 Microtask Queue.
-
直到 Microtask Queue 为空时,才会执行 Event Queue 中的 Event.
Dart 中只能知道 Event 处理的先后顺序,但是并不知道某个 Event 执行的具体时间点,因为它的处理模型是一个单线程循环,而不是基于时钟调度(即它的执行只是按照 Event 处理完,就开始循环下一个 Event,而与Java 中的 Thread 调度不一样,没有时间调度的概念),也就是我们既是指定另一个 Delay Time 的 Task,希望它在预期的时间后开始执行,它有可能不会在那个时间执行,需要看是否前面的 Event 是否已经 Dequeue 。
任务调度
当有代码可以在后续任务执行的时候,有两种方式,通过dart:async这个Lib中的API即可:
- 使用 Future 类,可以将任务加入到 Event Queue 的队尾
- 使用 scheduleMicrotask 函数,将任务加入到 Microtask Queue 队尾
当使用 EventQueue 时,需要考虑清楚,尽量避免 microtask queue 过于庞大,否则会阻塞其他事件的处理
new Future()
先来看一段代码
void testFuture() {
Future f = new Future(() => print("1"));
Future f1 = new Future(() => null);
Future f2 = new Future(() => null);
Future f3 = new Future(() => null);
f3.then((_) => print("2"));
f2.then((_) {
print("3");
new Future(() => print("4"));
f1.then((_) {
print("5");
});
});
f1.then((m) {
print("6");
});
print("7");
}
复制代码
大家来猜一猜上面的打印效果,打印结果是:
7
1
6
3
5
2
4
复制代码
是不是没有想到。下面我们来分析下执行流程
- main 函数执行完的时候,f, f1, f2, f3 会进入事件队列,这里 print( 7 )不进入队列 直接打印 7;
- 队列里面又执行 f 事件,发现 f 存在一个打印事件 print(1) 直接打印 1;
- 现在又执行 f1 事件,发现了一个 f1.then(m) => print('6') 直接打印 6;
- 执行到了 f2 事件,发现了 f2 事件里面有一个 打印事件 3,先打印一个 3
- 然后遇见了一个新的时间 f4,放入新的事件队列里面。
- 现在又发现了一个 f1 的事件 print('5'),这里的 f1 因为执行完了 返回的 null 所以要把该事件存放入 微队列 里面。微队列里面优先执行所以打印 5 ;
- 现在该执行到了 f2 事件了,发现了一个 print(2), 直接打印 2;
- 最后执行到了 f4 事件,发现了一个 print(4),直接打印 4;
注意:这里的 f,f1,f2,f3... 进入事件队列里面要把它们理解成是一个整体,不管其它地方 还有 f1.then 或者 f2.then 。最后都是一个整体。
注意
-
使用 new Future 将任务加入 event 队列。
-
Future 中的 then 并没有创建新的 Event 丢到 Event Queue 中,而只是一个普通的 Function Call ,
FutureTask 执行完后,立即开始执行。
-
如果在 then() 调用之前 Future 就已经执行完毕了,那么任务会被加入到 microtask 队列中,并且该任务会执行 then() 中注册的回调函数。
-
使用 Future.value 构造函数的时候,就会上一条一样,创建 Task 丢到 microtask Queue 中执行 then 传入的函数。
-
Future.sync 构造函数执行了它传入的函数之后,也会立即创建 Task 丢到 microtask Queue 中执行。
-
当任务需要延迟执行时,可以使用 new Future.delay() 来将任务延迟执行。
scheduleMicrotask();
看一段代码,然后再来分析它们的执行流程
//scheduleMicrotask
void testScheduleMicrotask() {
//918346572
scheduleMicrotask(() => print('s1'));
new Future.delayed(new Duration(seconds: 1), () => print('s2'));
new Future(() => print('s3')).then((_) {
print('s4');
scheduleMicrotask(() => print('s5'));
}).then((_) => print('s6'));
new Future(() => print('s10'))
.then((_) => new Future(() => print('s11')))
.then((_) => print('s12'));
new Future(() => print('s7'));
scheduleMicrotask(() => print('s8'));
print('s9');
}
复制代码
打印结果
s9
s1
s8
s3
s4
s6
s5
s10
s7
s11
s12
s2
复制代码
这一次大家自己分析了 咳咳 ~ 。
注意
- 如果可以,尽量将任务放入event 队列中。
- 使用 Future 的 then 方法或 whenComplete 方法来指定任务顺序。
- 为了保持你 app 的可响应性,尽量不要将大计算量的任务放入这两个队列。
- 大计算量的任务放入额外的 isolate 中。
生成器
同步生成器
//同步生成器
//调用getSyncGenerator 立刻返回 Iterable
void main() {
var it = getSyncGenerator(5).iterator;
// 调用moveNext方法时getSyncGenerator才开始执行
while (it.moveNext()) {
print(it.current);
}
}
//同步生成器: 使用sync*,返回的是Iterable对象
Iterable getSyncGenerator(int n) sync* {
print('start');
int k = n;
while (k > 0) {
//yield会返回moveNext为true,并等待 moveNext 指令
yield k--;
}
print('end');
}
复制代码
注意
- 使用 sync* ,返回的是 Iterable 对象。
- yield 会返回 moveNext 为 true ,并等待 moveNext 指令。
- 调用 getSyncGenerator 立即返回 Iterable 对象。
- 调用 moveNext 方法时 getSyncGenerator 才开始执行。
异步生成器
//异步生成器调用
// getAsyncGenerator立即返回Stream,只有执行了listen,函数才会开始执行
StreamSubscription subscription = getAsyncGenerator(5).listen(null);
subscription.onData((value) {
print(value);
if (value >= 2) {
subscription.pause(); //可以使用StreamSubscription对象对数据流进行控制
}
});
//异步生成器: 使用async*,返回的是Stream对象
Stream<int> getAsyncGenerator(int n) async* {
print('start');
int k = 0;
while (k < n) {
//yield不用暂停,数据以流的方式一次性推送,通过StreamSubscription进行控制
yield k++;
}
print('end');
}
复制代码
注意
- 使用 async* ,返回的是 Stream 对象。
- yield 不用暂停,数据以流的方式一次性推送,通过 StreamSubscription 进行控制。
- 调用 getAsyncGenerator 立即返回 Stream ,只有执行了 listen ,函数才会开始执行。
- listen 返回一个 StreamSubscription 对象进行流监听控制。
- 可以使用 StreamSubscription 对象对数据流进行控制。
递归生成器
异步
void main(){
//异步
getAsyncRecursiveGenerator(5).listen((value) => print(value));
}
//异步递归生成器
Stream<int> getAsyncRecursiveGenerator(int n) async* {
if (n > 0) {
yield n;
yield* getAsyncRecursiveGenerator(n - 1);
}
}
复制代码
同步
void main (){
//递归生成器
//同步
var it1 = getSyncRecursiveGenerator(5).iterator;
while (it1.moveNext()) {
print(it1.current);
}
}
//递归生成器:使用yield*
Iterable<int> getSyncRecursiveGenerator(int n) sync* {
if (n > 0) {
yield n;
yield* getSyncRecursiveGenerator(n - 1);
}
}
复制代码
注意
- yield* 以指针的方式传递递归对象,而不是整个同步对象。
隔离
Isolates
现代的浏览器以及移动浏览器都运行在多核 CPU 系统上。 要充分利用这些 CPU,开发者一般使用共享内存 数据来保证多线程的正确执行。然而, 多线程共享数据通常会导致很多潜在的问题,并导致代码运行出错。
所有的 Dart 代码在 isolates 中运行而不是线程。 每个 isolate 都有自己的堆内存,并且确保每个 isolate 的状态都不能被其他 isolate 访问。
元数据
(注解)-@deprecated
main() {
dynamic tv = new Television();
tv.activate();
tv.turnOn();
}
class Television {
@deprecated
void activate() {
turnOn();
}
void turnOn() {
print('Television turn on!');
}
}
复制代码
(注解)-@override
main() {
dynamic tv = new Television();
tv.activate();
tv.turnOn();
tv.turnOff();
}
class Television {
@deprecated
void activate() {
turnOn();
}
void turnOn() {
print('Television turn on!');
}
@override
noSuchMethod(Invocation mirror) {
print('没有找到方法');
}
}
复制代码
注意
- 所有的 Dart 代码都可以使用: @deprecated 和 @override。
(注解)-自定义
创建 todo.dart 文件
//todo.dart
class Todo {
final String who;
final String what;
const Todo({this.who, this.what});
}
复制代码
import 'todo.dart’;
main() {
dynamic tv = new Television();
tv.doSomething();
}
class Television {
@Todo(who: 'damon', what: 'create a new method')
void doSomething() {
print('doSomething');
}
}
复制代码
注意
- 在 java 中,如果自定义一个注解,需要添加 @Target 作用域注解,@Retention 注解类型注解,添加 @interface,然后定义注解参数。
- 构造方法定义为编译时常量
注释
单行注释
// 跟 Java 一样
复制代码
多行注释
/**跟 Java 一样**/
复制代码
文档注释
/// 跟 Java 一样
复制代码
总结
这 三篇基础 内容介绍了常见的 Dart 语言特性。 还有更多特性有待实现,但是新的特性不会破坏已有的代码。 更多信息请参考 Dart 语言规范 和 Effective Dart。
要了解 Dart 核心库的详情,请参考 Dart 核心库预览。