Dart異步編程學習札記
Dart是一門單線程,所以在開發(fā)中我們不需要像其他多線程語言一樣需要考慮資源競爭問題句葵,也沒有鎖的概念,而Dart編程中如果需要實現(xiàn)等待一個任務完成的同時進行別的任務的操作時卦羡,就需要使用異步操作來實現(xiàn)了功氨。
而要在Dart中執(zhí)行異步操作,則需要使用Future類刊懈。
Future的使用
最簡單的这弧,我們可以直接這樣寫:
void main() {
print("任務 1");
Future((){
print("任務 2");
});
print("任務 3");
}
此時控制臺打印:
flutter: 任務 1
flutter: 任務 3
flutter: 任務 2
可以看到俏讹,雖然任務 2在任務 3之前添加当宴,但是卻在任務3執(zhí)行完畢后才執(zhí)行。
而且與多線程不同泽疆,異步任務不是與同步任務同時進行户矢,而是要等待主線程空閑的時候,才會去執(zhí)行異步任務殉疼。
void main() {
print("任務 1");
Future(() {
print("任務 2");
});
sleep(Duration(seconds: 5));
print("任務 3");
}
執(zhí)行后:
flutter: 任務 1
flutter: 任務 3
flutter: 任務 2
吐槽一下Dart梯浪,單線程語言的性能真心不如多線程語言。瓢娜。挂洛。
如果我們需要在異步操作中修改一個變量的值,并且在后續(xù)的同步操作中獲取到該值眠砾,如:
void main() {
print("任務 1");
Future(() {
_data = "2";
print("任務 2");
});
print("任務 3");
print("data value is: ${_data}");
}
String _data = "0";
顯然這樣寫是無法得到我們想要的結果的虏劲,打印結果如下:
flutter: 任務 1
flutter: 任務 3
flutter: data value is: 0
flutter: 任務 2
要解決這個問題,可以使用await和async關鍵字。
await和async
首先我們將異步操作放到一個函數(shù)中, 并且這個函數(shù)顯然是一個異步函數(shù)柒巫,因此我們要在函數(shù)名后面添加一個async
關鍵字励堡,表示這是一個異步函數(shù)
僅僅一個async關鍵字是無法滿足我們需求的,我們還需要在異步操作前添加一個await
關鍵字堡掏,需要注意的是,await要在異步函數(shù)中才能使用应结。
void asyncTask() async {
await Future((){
_data = "2";
print("任務 2");
});
print("data value is: ${_data}");
}
打印結果為
flutter: 任務 2
flutter: data value is: 2
繼續(xù)對await關鍵字探究,如果我們在asyncTask函數(shù)中添加任務4泉唁,是否會先執(zhí)行任務4呢鹅龄?
void asyncTask() async {
await Future((){
_data = "2";
print("任務 2");
});
print("data value is: ${_data}");
print("任務 4");
}
flutter: 任務 2
flutter: data value is: 2
顯然任務4最后才執(zhí)行,而寫在main函數(shù)中的任務3先執(zhí)行了亭畜,由此我們可以判斷扮休,await關鍵字作用域為所在異步函數(shù)內,聲明await后贱案,異步函數(shù)后續(xù)操作都必須等待await修飾的異步操作執(zhí)行完畢后才能執(zhí)行肛炮。
處理Future結果
查看源文件,我們發(fā)現(xiàn)Future((){})
其實是一個工廠構造函數(shù)宝踪,我們拿到future對象侨糟,進行一些操作
Future.then()
這是一個用來注冊Future完成是要調用的回調,它會在future完成后立馬執(zhí)行瘩燥,而在then的回調中有兩個參數(shù)秕重,分別是:
Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
顯而易見,onValue是任務成功的回調厉膀,而onError則是事件錯誤的回調溶耘。
另外,onValue回調中帶有一個參數(shù)服鹅,我們可以通過下面的代碼看到它的用法:
Future((){
print("任務 2");
return "value success";
}).then((value) {
print("then 任務");
print(value);
});
執(zhí)行結果:
flutter: 任務 2
flutter: then 任務
flutter: value success
顯然凳兵,onValue中的value參數(shù),實際上就是Future中return的值企软。這個我們就可以實現(xiàn)不用await等待庐扫,通過then拿到異步操作返回的結果。這種寫法仗哨,顯然比await清晰許多形庭。
繼續(xù)探究onError:
Future(() {
print("任務 2");
throw Exception("future error");
}).then((value) {
print("then 任務");
print(value);
}, onError: (error) {
print(error);
});
執(zhí)行后結果:
flutter: 任務 2
flutter: Exception: future error
可以到,如果future中拋出了一個錯誤厌漂,那么我們可以onError回調中處理這個錯誤萨醒,并且onVaule回調將不會執(zhí)行。
Future.catchError()
很明顯苇倡,這個回調也是用來處理error的:
Future(() {
print("任務 2");
throw Exception("future error");
}).catchError((error) {
print(error);
});
結果:
flutter: 任務 2
flutter: Exception: future error
另外富纸,如果我們這么寫:
final future = Future(() {
throw Exception("error");
});
future.catchError((error) {
print("catchError $error");
});
future.then((value) {
print("then 任務");
}, onError: (e) {
print("onError $e");
});
結果會出現(xiàn):
flutter: catchError Exception: error
flutter: onError Exception: error
從上面的結果可以看出囤踩,onError和catchError是可以同樣捕獲錯誤的
我們接著探究:
final future = Future(() {
throw Exception("error");
});
future.catchError((error) {
print("catchError $error");
}).then((value) {
print("then 任務");
}, onError: (e) {
print("onError $e");
});
結果為:
flutter: catchError Exception: error
flutter: then 任務
而如果我們這么寫
final future = Future(() {
throw Exception("error");
});
future.then((value) {
print("then 任務");
}, onError: (e) {
print("onError $e");
}).catchError((error) {
print("catchError $error");
});
結果為
flutter: onError Exception: error
因此,我們可以得出結論:future采用鏈式編程的時候晓褪,如果當前future拋出異常高职,錯誤信息會由下一個future的onError或catchError接受處理,并且不會繼續(xù)往下一個future傳遞辞州。
Future.whenComplete()
和then相似的是,whenComplete也是在Future執(zhí)行完畢后調用寥粹,但不同的是变过,不管future中是否拋出異常,whenComplete都會執(zhí)行
Future(() {
print("任務 2");
throw Exception("future error");
}).whenComplete(() {
print("whenComplete");
}).catchError((error) {
print(error);
});
flutter: 任務 2
flutter: whenComplete
flutter: Exception: future error
多個Future的情況
執(zhí)行以下代碼:
Future(() {
print("future 1");
}).then((value) => print("then 1"));
Future(() {
print("future 2");
}).then((value) => print("then 2"));
Future(() {
print("future 3");
}).then((value) => print("then 3"));
執(zhí)行結果:
flutter: future 1
flutter: then 1
flutter: future 2
flutter: then 2
flutter: future 3
flutter: then 3
可以看到涝涤,多個Future的情況媚狰,執(zhí)行順序是按照添加的順序執(zhí)行的,如果Future后有then回調阔拳,那么執(zhí)行當前future執(zhí)行完后會立馬執(zhí)行then回調崭孤,而不是執(zhí)行下一個Future。
顯然糊肠,所有Future都是被添加到某種隊列中辨宠,按照先進先出的方式進行執(zhí)行。查看官方文檔可以知道货裹,確實在Dart中有事件隊列(event queue)嗤形、微任務隊列(microtask queue)兩種隊列,而Future異步任務弧圆,是被添加到事件隊列中赋兵。
Dart的事件循環(huán)(event loop)
Dart中,有兩種隊列:
1. 事件隊列(event queue)搔预,包含所有的外來事件:I/O霹期、mouse events、drawing events拯田、timers历造、isolate之間的信息傳遞。
2. 微任務隊列(microtask queue)勿锅,表示一個短時間內就會完成的異步任務帕膜。它的優(yōu)先級最高,高于event queue溢十,只要隊列中還有任務垮刹,就可以一直霸占著事件循環(huán)。microtask queue添加的任務主要是由 Dart內部產(chǎn)生张弛。
在每一次事件循環(huán)中荒典,Dart總是先去第一個微任務隊列中查詢是否有可執(zhí)行的任務酪劫,如果沒有,才會處理后續(xù)的事件隊列
實踐一下:
void main() {
print("任務 1");
Future((){
print("任務 2");
});
scheduleMicrotask((){
print("任務 4");
});
print("任務 3");
}
執(zhí)行結果為
flutter: 任務 1
flutter: 任務 3
flutter: 任務 4
flutter: 任務 2
可以看到寺董,盡管任務2先被添加到事件隊列覆糟,但是最終的執(zhí)行結果是微任務隊列中的任務4先被執(zhí)行。
通過以上的解釋遮咖,我們就可以分析下面代碼的打印順序
Future x1 = Future(() => null);
x1.then((value) {
print('6');
scheduleMicrotask(() => print('7'));
}).then((value) => print('8'));
Future x = Future(() => print('1'));
x.then((value) {
print('4');
Future(() => print('9'));
}).then((value) => print('10'));
Future(() => print('2'));
scheduleMicrotask(() => print('3'));
print('5');
- 首先滩字,同步任務 5 肯定是先被執(zhí)行,
- 因為 7 是在x1的then中才會被添加御吞,所以先執(zhí)行的是外部已經(jīng)添加到微任務隊列中的3
- 3執(zhí)行完后微任務隊列為空麦箍,走到事件對列,此時事件隊列中為x1,1,2,所以會先執(zhí)行x1的任務陶珠,而x1中任務為空挟裂,會走到then中6,所以下一個打印的是6揍诽,
- 而 6 執(zhí)行完后還有一個then诀蓉,所以也會立即執(zhí)行then中的8,
- 此時暑脆,7已經(jīng)被添加到微任務隊列中渠啤,所以下一個執(zhí)行的是7,
- 微任務隊列又空了饵筑,繼續(xù)執(zhí)行事件隊列下一個任務1埃篓,
- 而后執(zhí)行then中的4,下一步操作中根资,
- 9也被添加到事件隊列中架专,所以此時事件隊列中還有2和9,
- 4打印完后立即執(zhí)行then中的10玄帕,
- 隨后再將事件隊列中剩余的任務2和9執(zhí)行完畢部脚,所以最終的打印順序為:
5,3裤纹,6委刘,8,7鹰椒,1锡移,4,10漆际,2淆珊,9
執(zhí)行看看:
flutter: 5
flutter: 3
flutter: 6
flutter: 8
flutter: 7
flutter: 1
flutter: 4
flutter: 10
flutter: 2
flutter: 9
與分析一致。
總結
盡管Dart是一門單線程線程語言奸汇,但我們仍然可以使用異步操作去執(zhí)行實現(xiàn)一些諸如網(wǎng)絡獲取數(shù)據(jù)施符,寫入數(shù)據(jù)庫等耗時操作往声,避免阻礙正常事件的執(zhí)行。