Dart是一個在單線程中運行的程序骨杂,這意味著:如果程序在執(zhí)行中遇到一個需要長時間的執(zhí)行的操作涂身,程序?qū)粌鼋Y(jié)雄卷。為了避免造成程序的凍結(jié)搓蚪,可以使用異步操作使程序在等待一個耗時操作完成時繼續(xù)處理其他工作。在Dart中丁鹉,可以使用Future
對象來表示異步操作的結(jié)果妒潭。
Dart的消息循環(huán)機制
在進入正題之前,我們先看一下Dart的消息循環(huán)機制:
簡單總結(jié)一下揣钦,詳細內(nèi)容可以看文章The Event Loop and Dart
Dart中事件循環(huán)的一些主要概念:
- Dart從兩個隊列執(zhí)行任務(wù):event事件隊列和microtask微任務(wù)隊列雳灾;
- Dart的方法是不會被其他Dart代碼打斷的,當main執(zhí)行完成后冯凹,main isolate的線程就會去逐一處理消息隊列中的消息
- 事件隊列具有來自Dart(Future谎亩,Timer,Isolate Message等)和系統(tǒng)(用戶輸入,I/O等)匈庭;
- 微任務(wù)隊列目前僅包含來自Dart;
- 事件循環(huán)會優(yōu)先處理微任務(wù)隊列夫凸,microtask清空之后才將event事件隊列中的下一個項目出隊并處理。
- 一旦兩個隊列都為空阱持,則應(yīng)用程序已完成工作夭拌,并且(取決于其嵌入程序)可以退出。
- main()函數(shù)以及微任務(wù)和事件隊列中的所有項目都在Dart應(yīng)用程序的main isolate上運行衷咽。
什么是Future
Future<T>表示一個指定類型的異步操作結(jié)果(不需要結(jié)果可以使用Future<void>)當一個返回 future 對象的函數(shù)被調(diào)用時:
講函數(shù)放入隊列等待執(zhí)行并返回一個未完成的Future對象
當函數(shù)操作執(zhí)行完成鸽扁,F(xiàn)uture對象變?yōu)橥瓿刹y帶一個值或一個錯誤
上面兩條分別對應(yīng)兩個狀態(tài):運行狀態(tài)(pending),表示任務(wù)還未完成镶骗,也沒有返回值
完成狀態(tài)(completed),表示任務(wù)已經(jīng)結(jié)束(無論失敗還是成功)
例如:
# demo1
main() {
Future f1 = new Future(() {
print("我是第一個");
});
f1.then((_) => print("f1 then"));
print("我是main");
}
# print:
# 我是main
# 我是第一個
# f3 then
觀察程序輸出桶现,首先執(zhí)行完main函數(shù)然后再去執(zhí)行任務(wù)棧中的內(nèi)容,在該例中也就是我們使用Future假如到event任務(wù)棧中的任務(wù)<u>鼎姊,then
中的方法會在Future處于完成態(tài)(completed)時立馬執(zhí)行</u>巩那,之后我們再詳細講解。
Dart提供了數(shù)種創(chuàng)建Future的方法此蜈,其中最基本的為:
factory Future(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
Timer.run(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
demo1中所使用的就是這種方式創(chuàng)建的Future即横。
其他創(chuàng)建Future的方式包括:
- Future.value():返回一個指定值的Future
- Future.delayed():返回一個延時執(zhí)行的Future
main() {
Future.delayed(Duration(milliseconds: 200),(){
print("我是延遲的Future");
});
var future = Future.value("我是Future");
future.then((value) => print(value));
}
# print:
# 我是Future
# 我是延遲的Future
這端代碼執(zhí)行了兩個分支:
- main()方法
- event隊列
Future中的任務(wù)調(diào)度
前面講過:當Future執(zhí)行完成后,then()注冊的回調(diào)函數(shù)會立即執(zhí)行裆赵,但是then中的函數(shù)并不會被添加到事件隊列中东囚,只是在事件隊列中的任務(wù)被執(zhí)行完成后才被立刻執(zhí)行(可以理解為:將網(wǎng)絡(luò)請求放在隊列中進行執(zhí)行,拿到結(jié)果后在then中刷新UI)战授。
main() {
Future f1 = new Future(() => null);
Future f2 = new Future(() => null);
Future f3 = new Future(() => {print("創(chuàng)建f3")});
f3.then((value) => print("我是f3"));
f2.then((value) => print("我是f2"));
f1.then((value) => print("我是f1"));
}
上面程序的輸出結(jié)果為:
我是f1
我是f2
創(chuàng)建f3
我是f3
首先页藻,任務(wù)棧符合以FIFO的方式運行,f1,f2,f3一次被加入到任務(wù)棧植兰,then()注冊的函數(shù)并不會被添加到隊列份帐,也不會直接運行。當任務(wù)棧中任務(wù)被執(zhí)行后楣导,立刻運行then中的函數(shù)废境,依次類推⊥卜保可以看到噩凹,then中的回調(diào)函數(shù)執(zhí)行的順序并不取決于注冊的順序,而僅僅與其Future被加入到任務(wù)棧的順序有關(guān)毡咏。
注意:new Future(() => null)和new Future(null)有本質(zhì)上的區(qū)別驮宴,一個函數(shù)體為空,什么都不做呕缭;一個是參數(shù)為空堵泽,不存在函數(shù)修己。
稍微修改一下上例中的代碼:
main() {
Future f1 = new Future(() => null);
Future f2 = new Future(() => null);
Future f3 = new Future(() => {print("創(chuàng)建f3")});
f3.then((_) => print("我是f3"));
f2.then((_) {
print("我是f2");
new Future(() => print("我是一個新的"));
f1.then((_) {
print("我是f1");
});
}).then((value) => print("我還是f2"));
}
執(zhí)行結(jié)果為:
我是f2
我還是f2
我是f1
創(chuàng)建f3
我是f3
我是一個新的
先看一下then的定義:
Future<R> then<R>(FutureOr<R> onValue(T value), {Function? onError});
這里涉及到兩個關(guān)鍵點:
- 如果Future在then被調(diào)用之前已經(jīng)完成,那么then中的函數(shù)會被作為任務(wù)添加到microtask隊列中迎罗;
- then會返回新的新的Future箩退,并且該Future在
onValue
(then中注冊的回調(diào)函數(shù))或者onError被執(zhí)行時就已經(jīng)處于完成狀態(tài)了。 - 如果onValue(回調(diào)函數(shù))返回值為一個Future佳谦,那么then返回的Future將會在
onValue
返回的future執(zhí)行完成后處于完成狀態(tài)
關(guān)于后面兩點:
main() async {
Future f2 = new Future(() => null);
f2.then((_) {
print("我是真正的f2");
Future f1 = new Future(() => null);
f1.then((value) => print("我是f1"));
})
.then((value) => print(value))
.then((value) => print("我還是f2嗎"));
}
輸出結(jié)果為:
我是真正的f2
我還是f2嗎
我是f1
其中戴涝,每個then都會返回一個新的Future,而該future會在onValue
钻蔑,也就是回調(diào)函數(shù)執(zhí)行時處于完成狀態(tài)啥刻,然后立即執(zhí)行該新future的回調(diào)函數(shù)。
稍微修改代碼:
main() {
Future f2 = new Future(() => null);
f2.then((_) {
print("我是真正的f2");
Future f1 = new Future(() => null);
f1.then((value) => print("我是f1"));
return new Future(() => {print("全新的Future")});
})
.then((value) => print("我還是f2嗎"))
.then((value) => print("我不是了"));
}
運行結(jié)果為:
我是真正的f2
我是f1
全新的Future
我還是f2嗎
我不是了
注意咪笑,then方法本身會返回一個future可帽。在then中的函數(shù)也返回了一個Future,而then所返回的future會緊跟著函數(shù)返回的future之后處于完成狀態(tài)再執(zhí)行后續(xù)回調(diào)函數(shù)窗怒。
總結(jié)一下:
- 當Future任務(wù)完成后映跟,then()注冊的回調(diào)函數(shù)會立即執(zhí)行。需注意的是扬虚,then()注冊的函數(shù)并不會添加到事件隊列中努隙,回調(diào)函數(shù)只是在事件循環(huán)中任務(wù)完成后被調(diào)用。
- 如果Future在then()被調(diào)用之前已經(jīng)完成計算辜昵,那么任務(wù)會被添加到微任務(wù)隊列中荸镊,并且該任務(wù)會執(zhí)行then()中注冊的回調(diào)函數(shù)。
- then會返回新的新的Future堪置,并且該Future在
onValue
(then中注冊的回調(diào)函數(shù))或者onError被執(zhí)行時就已經(jīng)處于完成狀態(tài)了躬存。 - 如果onValue(回調(diào)函數(shù))返回值為一個Future,那么then返回的Future將會在
onValue
返回的future執(zhí)行完成后處于完成狀態(tài)
如何處理異步操作的結(jié)果
包括上面提到then舀锨,有三種方法處理Future的結(jié)果:
- then: 處理操作執(zhí)行結(jié)果或者錯誤并返回一個新的Future
- catchError: 注冊一個處理錯誤的回調(diào)
- whenComplete:類似final岭洲,無論錯誤還是正確,F(xiàn)uture執(zhí)行結(jié)束后總是被調(diào)用
then中的onError只能處理當前Future中的錯誤坎匿,而catchError能處理整條調(diào)用鏈上的任何錯誤盾剩。
main() async {
Future f1 = new Future(() => null);
Future f2 = new Future(() => null);
f1
.then((value) {
return Future.error("錯誤了");
})
.then((value) => print("執(zhí)行成功了嗎"), onError: (error) => print(error))
.then((value) => Future.error('錯了!'))
.catchError((error) => {print("我也發(fā)現(xiàn):$error")});
f2.then((_) {
print("我是f2");
}).whenComplete(() => print("完成了"));
}
輸出結(jié)果為:
錯誤了
我也發(fā)現(xiàn):錯了!
我是f2
完成了
async和await
上面講了Future的基本用法碑诉,以及使用Future API處理數(shù)據(jù)的方法彪腔。但是這種方法存在一個問題:使用鏈式調(diào)用的方式把多個future連接在一起侥锦,會嚴重降低代碼的可讀性进栽。
可以使用async和await關(guān)鍵字實現(xiàn)異步的功能。async和await可以幫助我們像寫同步代碼一樣編寫異步代碼
main() async {
Future f1 = new Future.delayed(Duration(milliseconds: 2000),() {
return "我是第一個";
});
Future f2 = new Future(() {
return "我是第二個";
});
f2.then((value) => print("哦哦哦"));
print("開始了:${DateTime.now()}");
print("${await f1}:${await f2}");
print("結(jié)束了:${DateTime.now()}");
}
輸出:
開始了:2020-10-13 15:37:16.871165
哦哦哦
我是第一個:我是第二個
結(jié)束了:2020-10-13 15:37:18.877511
注意:await只能在async函數(shù)里出現(xiàn)
要想改寫異步代碼恭垦,只需要在函數(shù)中添加async
關(guān)鍵字
String getAString() {
return "我是一個字符串";
}
## 改寫為異步代碼
Future<String> getAString() async{
return "我是一個字符串";
}
需要注意的是快毛,在普通函數(shù)中格嗅,return返回的為T,那么在async函數(shù)中返回的是Future<T>唠帝。但是并不需要顯示的去指明返回的類型屯掖,Dart會自動將返回值包裝成Future對象。但是襟衰,如果原函數(shù)返回的為Future<T>贴铜,在async函數(shù)中返回的仍然是是Future<T>。若async函數(shù)沒有返回值瀑晒,那么Dart會返回一個null值的Future绍坝。
main() {
print("main函數(shù)開始了");
firstString();
secondString();
thirdString();
print("main函數(shù)結(jié)束了");
}
firstString() async{
print("firstString函數(shù)開始了");
Future.delayed(Duration(milliseconds: 300), () {
return "我是一個字符串";
}).then((value) => {print(value)});
print("firstString函數(shù)結(jié)束了");
}
secondString() {
print("我是二個字符串");
}
thirdString() {
print("我是三個字符串");
}
上面代碼的輸出結(jié)果為:
main函數(shù)開始了
firstString函數(shù)開始了
firstString函數(shù)結(jié)束了
我是二個字符串
我是三個字符串
main函數(shù)結(jié)束了
我是一個字符串
注意觀察代碼的執(zhí)行順序,函數(shù)按照順序執(zhí)行苔悦,首先執(zhí)行main函數(shù)轩褐,接著按照順序執(zhí)行firstString()、secondString()thirdString()玖详。Future.delayed并不會阻礙任何代碼的執(zhí)行把介,這符合上文中講的非阻塞調(diào)用,F(xiàn)uture并不會阻塞它所在函數(shù)的執(zhí)行蟋座。
我們稍微修改一下代碼:
main() {
print("main函數(shù)開始了");
firstString();
secondString();
thirdString();
print("main函數(shù)結(jié)束了");
}
firstString() async {
print("firstString函數(shù)開始了");
Future future = Future.delayed(Duration(milliseconds: 300), () {
return "我是一個字符串";
});
print(await future);
print("firstString函數(shù)結(jié)束了");
}
secondString() {
print("我是二個字符串");
}
thirdString() {
print("我是三個字符串");
}
輸出結(jié)果為:
main函數(shù)開始了
firstString函數(shù)開始了
我是二個字符串
我是三個字符串
main函數(shù)結(jié)束了
我是一個字符串
firstString函數(shù)結(jié)束了
對比兩次結(jié)果不難發(fā)現(xiàn)拗踢,async和await關(guān)鍵字使得原本非阻塞式的函數(shù)變的同步了,成了阻塞函數(shù)了向臀。函數(shù)遇到Future秒拔,再其未執(zhí)行完之前一直處于阻塞狀態(tài)。但是main函數(shù)依舊正常執(zhí)行飒硅,并不會被async函數(shù)所阻塞砂缩。async和await只會作用于當前函數(shù),并不會對其他外部函數(shù)造成執(zhí)行上的影響三娩。
await也可以幫助我們在執(zhí)行下個語句之前確保當前語句執(zhí)行完畢:
main() async {
print("main函數(shù)開始了:${DateTime.now()}");
print(await firstString());
print(await secondString());
print(await thirdString());
print("main函數(shù)結(jié)束了:${DateTime.now()}");
}
firstString() {
return Future.delayed(Duration(milliseconds: 300), () {
return "我是一個字符串";
});
}
secondString() {
return Future.delayed(Duration(milliseconds: 200), () {
return "我是二個字符串";
});
}
thirdString() {
return Future.delayed(Duration(milliseconds: 100), () {
return "我是三個字符串";
});
}
輸出結(jié)果為:
main函數(shù)開始了:2020-10-13 16:24:46.897353
我是一個字符串
我是二個字符串
我是三個字符串
main函數(shù)結(jié)束了:2020-10-13 16:24:47.527151