前言一:接下來一段時間我會陸續更新一些列Flutter文字教程前端
更新進度: 每週至少兩篇;vue
更新地點: 首發於公衆號,次日更新於掘金、思否等地方;面試
更多交流: 能夠添加個人微信 372623326,關注個人微博:coderwhy算法
但願你們能夠 幫忙轉發,點擊在看,給我更多的創做動力。編程
前言二:在寫這篇文章以前,我一直在猶豫,要不要在這裏講解Dart的異步相關話題,由於這部份內容很容易讓初學者望而卻步:json
一、關於單線程和異步之間的關係,比較容易讓人迷惑,雖然我必定會用本身的方式儘量讓你聽懂。數組
二、大量的異步操做方式(Future、await、async等),目前你看不到具體的應用場景。(好比你學習過前端中的Promise、await、async可能會比較簡單,可是我會假設你沒有這樣的基礎)。微信
不過,聽我說:若是這一個章節你學完以後還有不少疑惑,沒有關係,在後面用到相關知識時,回頭來看,你會豁然開朗。網絡
咱們先來搞清楚Dart是如何搞定異步操做的數據結構
開發中的耗時操做:
如何處理耗時的操做呢?
我以前碰到不少開發者都對單線程的異步操做充滿了問號???
其實它們並不衝突:
阻塞式調用和非阻塞式調用
若是想搞懂這個點,咱們須要知道操做系統中的阻塞式調用
和非阻塞式調用
的概念。
咱們用一個生活中的例子來模擬:
外賣的動做
就是咱們的調用,拿到最後點的外賣
就是咱們要等待的結果。而咱們開發中的不少耗時操做,均可以基於這樣的 非阻塞式調用
:
非阻塞方式的工做
;基於事件的回調機制
;這些操做都不會阻塞咱們單線程的繼續執行,咱們的線程在等待的過程當中能夠繼續去作別的事情:喝杯咖啡、打把遊戲,等真正有了響應,再去進行對應的處理便可。
這時,咱們可能有兩個問題:
單線程模型中主要就是在維護着一個事件循環(Event Loop)。
事件循環是什麼呢?
咱們來寫一個事件循環的僞代碼:
// 這裏我使用數組模擬隊列, 先進先出的原則
List eventQueue = [];
var event;
// 事件循環從啓動的一刻,永遠在執行
while (true) {
if (eventQueue.length > 0) {
// 取出一個事件
event = eventQueue.removeAt(0);
// 執行該事件
event();
}
}
複製代碼
當咱們有一些事件時,好比點擊事件、IO事件、網絡事件時,它們就會被加入到eventLoop
中,當發現事件隊列不爲空時發現,就會取出事件,而且執行。
這裏咱們來看一段僞代碼,理解點擊事件和網絡請求的事件是如何被執行的:
RaisedButton(
child: Text('Click me'),
onPressed: () {
final myFuture = http.get('https://example.com');
myFuture.then((response) {
if (response.statusCode == 200) {
print('Success!');
}
});
},
)
複製代碼
這些代碼是如何放在事件循環中執行呢?
儘管onPressed和then中的回調有一些差別,可是它們對於事件循環來講,都是告訴它:我有一段代碼須要執行,快點幫我完成。
Dart中的異步操做主要使用Future以及async、await。
若是你以前有過前端的ES六、ES7編程經驗,那麼徹底能夠將Future理解成Promise,async、await和ES7中基本一致。
可是若是沒有前端開發經驗,Future以及async、await如何理解呢?
我思考了好久,這個Future到底應該如何講解
咱們先來看一個例子吧:
import "dart:io";
main(List<String> args) {
print("main function start");
print(getNetworkData());
print("main function end");
}
String getNetworkData() {
sleep(Duration(seconds: 3));
return "network data";
}
複製代碼
這段代碼會運行怎麼的結果呢?
main function start
// 等待3秒
network data
main function end
複製代碼
顯然,上面的代碼不是咱們想要的執行效果,由於網絡請求阻塞了main函數,那麼意味着其後全部的代碼都沒法正常的繼續執行。
咱們來對咱們上面的代碼進行改進,代碼以下:
import "dart:io";
main(List<String> args) {
print("main function start");
print(getNetworkData());
print("main function end");
}
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
return "network data";
});
}
複製代碼
咱們來看一下代碼的運行結果:
main function start
Instance of 'Future<String>'
main function end
複製代碼
獲取Future獲得的結果
有了Future以後,如何去獲取請求到的結果:經過.then的回調:
main(List<String> args) {
print("main function start");
// 使用變量接收getNetworkData返回的future
var future = getNetworkData();
// 當future實例有返回結果時,會自動回調then中傳入的函數
// 該函數會被放入到事件循環中,被執行
future.then((value) {
print(value);
});
print(future);
print("main function end");
}
複製代碼
上面代碼的執行結果:
main function start
Instance of 'Future<String>'
main function end
// 3s後執行下面的代碼
network data
複製代碼
執行中出現異常
若是調用過程當中出現了異常,拿不到結果,如何獲取到異常的信息呢?
import "dart:io";
main(List<String> args) {
print("main function start");
var future = getNetworkData();
future.then((value) {
print(value);
}).catchError((error) { // 捕獲出現異常時的狀況
print(error);
});
print(future);
print("main function end");
}
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
// 再也不返回結果,而是出現異常
// return "network data";
throw Exception("網絡請求出現錯誤");
});
}
複製代碼
上面代碼的執行結果:
main function start
Instance of 'Future<String>'
main function end
// 3s後沒有拿到結果,可是咱們捕獲到了異常
Exception: 網絡請求出現錯誤
複製代碼
補充一:上面案例的小結
咱們經過一個案例來學習了一些Future的使用過程:
補充二:Future的兩種狀態
事實上Future在執行的整個過程當中,咱們一般把它劃分紅了兩種狀態:
狀態一:未完成狀態(uncompleted)
狀態二:完成狀態(completed)
Dart官網有對這兩種狀態解析,之因此貼出來是區別於Promise的三種狀態
補充三:Future的鏈式調用
上面代碼咱們能夠進行以下的改進:
import "dart:io";
main(List<String> args) {
print("main function start");
getNetworkData().then((value1) {
print(value1);
return "content data2";
}).then((value2) {
print(value2);
return "message data3";
}).then((value3) {
print(value3);
});
print("main function end");
}
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
// 再也不返回結果,而是出現異常
return "network data1";
});
}
複製代碼
打印結果以下:
main function start
main function end
// 3s後拿到結果
network data1
content data2
message data3
複製代碼
補充四:Future其餘API
Future.value(value)
main(List<String> args) {
print("main function start");
Future.value("哈哈哈").then((value) {
print(value);
});
print("main function end");
}
複製代碼
打印結果以下:
main function start
main function end
哈哈哈
複製代碼
疑惑:爲何當即執行,可是哈哈哈
是在最後打印的呢?
Future.error(object)
main(List<String> args) {
print("main function start");
Future.error(Exception("錯誤信息")).catchError((error) {
print(error);
});
print("main function end");
}
複製代碼
打印結果以下:
main function start
main function end
Exception: 錯誤信息
複製代碼
Future.delayed(時間, 回調函數)
main(List<String> args) {
print("main function start");
Future.delayed(Duration(seconds: 3), () {
return "3秒後的信息";
}).then((value) {
print(value);
});
print("main function end");
}
複製代碼
若是你已經徹底搞懂了Future,那麼學習await、async應該沒有什麼難度。
await、async是什麼呢?
同步的代碼格式
,去實現異步的調用過程
。咱們已經知道,Future能夠作到不阻塞咱們的線程,讓線程繼續執行,而且在完成某個操做時改變本身的狀態,而且回調then或者errorCatch回調。
如何生成一個Future呢?
Talk is cheap. Show me the code.
咱們來對以前的Future異步處理代碼進行改造,改爲await、async的形式。
咱們知道,若是直接這樣寫代碼,代碼是不能正常執行的:
"network data"
去使用import "dart:io";
main(List<String> args) {
print("main function start");
print(getNetworkData());
print("main function end");
}
String getNetworkData() {
var result = Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "請求到的數據:" + result;
}
複製代碼
如今我使用await修改下面這句代碼:
Future.delayed
函數前加了一個await。Future.delayed
的執行完畢,而且等待它的結果。String getNetworkData() {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "請求到的數據:" + result;
}
複製代碼
修改後執行代碼,會看到以下的錯誤:
getNetworkData
函數定義成async函數。繼續修改代碼以下:
String getNetworkData() async {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "請求到的數據:" + result;
}
複製代碼
運行代碼,依然報錯(心想:你妹啊):
繼續修改代碼以下:
Future<String> getNetworkData() async {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "請求到的數據:" + result;
}
複製代碼
這段代碼應該是咱們理想當中執行的代碼了
我這裏給出了一個在Flutter項目中,讀取一個本地的json文件,而且轉換成模型對象,返回出去的案例;
這個案例做爲你們學習前面Future和await、async的一個參考,我並不打算展開來說,由於須要用到Flutter的相關知識;
後面我會在後面的案例中再次講解它在Flutter中我使用的過程當中;
讀取json案例代碼(瞭解一下便可)
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'dart:async';
main(List<String> args) {
getAnchors().then((anchors) {
print(anchors);
});
}
class Anchor {
String nickname;
String roomName;
String imageUrl;
Anchor({
this.nickname,
this.roomName,
this.imageUrl
});
Anchor.withMap(Map<String, dynamic> parsedMap) {
this.nickname = parsedMap["nickname"];
this.roomName = parsedMap["roomName"];
this.imageUrl = parsedMap["roomSrc"];
}
}
Future<List<Anchor>> getAnchors() async {
// 1.讀取json文件
String jsonString = await rootBundle.loadString("assets/yz.json");
// 2.轉成List或Map類型
final jsonResult = json.decode(jsonString);
// 3.遍歷List,而且轉成Anchor對象放到另外一個List中
List<Anchor> anchors = new List();
for (Map<String, dynamic> map in jsonResult) {
anchors.add(Anchor.withMap(map));
}
return anchors;
}
複製代碼
在前面學習學習中,咱們知道Dart中有一個事件循環(Event Loop)來執行咱們的代碼,裏面存在一個事件隊列(Event Queue),事件循環不斷從事件隊列中取出事件執行。
可是若是咱們嚴格來劃分的話,在Dart中還存在另外一個隊列:微任務隊列(Microtask Queue)。
事件循環
都是優先執行微任務隊列
中的任務,再執行 事件隊列
中的任務;那麼在Flutter開發中,哪些是放在事件隊列,哪些是放在微任務隊列呢?
說道這裏,你可能已經有點凌亂了,在Dart的單線程中,代碼究竟是怎樣執行的呢?
main函數中的代碼
會優先執行;微任務隊列(Microtask Queue)
中的全部任務;事件隊列(Event Queue)
中的全部任務;在開發中,咱們能夠經過dart中async下的scheduleMicrotask來建立一個微任務:
import "dart:async";
main(List<String> args) {
scheduleMicrotask(() {
print("Hello Microtask");
});
}
複製代碼
在開發中,若是咱們有一個任務不但願它放在Event Queue中依次排隊,那麼就能夠建立一個微任務了。
Future的代碼是加入到事件隊列仍是微任務隊列呢?
Future中一般有兩個函數執行體:
那麼它們是加入到什麼隊列中的呢?
// future_1加入到eventqueue中,緊隨其後then_1被加入到eventqueue中
Future(() => print("future_1")).then((_) => print("then_1"));
// Future沒有函數執行體,then_2被加入到microtaskqueue中
Future(() => null).then((_) => print("then_2"));
// future_三、then_3_a、then_3_b依次加入到eventqueue中
Future(() => print("future_3")).then((_) => print("then_3_a")).then((_) => print("then_3_b"));
複製代碼
咱們根據前面的規則來學習一個極的代碼執行順序
案例:
import "dart:async";
main(List<String> args) {
print("main start");
Future(() => print("task1"));
final future = Future(() => null);
Future(() => print("task2")).then((_) {
print("task3");
scheduleMicrotask(() => print('task4'));
}).then((_) => print("task5"));
future.then((_) => print("task6"));
scheduleMicrotask(() => print('task7'));
Future(() => print('task8'))
.then((_) => Future(() => print('task9')))
.then((_) => print('task10'));
print("main end");
}
複製代碼
代碼執行的結果是:
main start
main end
task7
task1
task6
task2
task3
task5
task4
task8
task9
task10
複製代碼
代碼分析:
main start
和main end
先執行,沒有任何問題;過程當中
,會將一些任務分別加入到EventQueue
和MicrotaskQueue
中;scheduleMicrotask
函數調用,因此它被最先加入到MicrotaskQueue
,會被先執行;EventQueue
,task1被添加到EventQueue
中被執行;final future = Future(() => null);
建立的future的then被添加到微任務中,微任務直接被優先執行,因此會執行task6;EventQueue
中添加task二、task三、task5被執行;scheduleMicrotask
,那麼在執行完此次的EventQueue
後會執行,因此在task5後執行task4(注意:scheduleMicrotask
的調用是做爲task3的一部分代碼,因此task4是要在task5以後執行的)EventQueue
被執行;事實上,上面的代碼執行順序有可能出如今面試中,咱們開發中一般不會出現這種複雜的嵌套,而且須要徹底搞清楚它的執行順序;
可是,瞭解上面的代碼執行順序,會讓你對EventQueue
和microtaskQueue
有更加深入的理解。
在Dart中,有一個Isolate的概念,它是什麼呢?
在 Isolate 中,資源隔離作得很是好,每一個 Isolate 都有本身的 Event Loop 與 Queue,
可是,若是隻有一個Isolate,那麼意味着咱們只能永遠利用一個線程,這對於多核CPU來講,是一種資源的浪費。
若是在開發中,咱們有很是多耗時的計算,徹底能夠本身建立Isolate,在獨立的Isolate中完成想要的計算操做。
如何建立Isolate呢?
建立Isolate是比較簡單的,咱們經過Isolate.spawn
就能夠建立了:
import "dart:isolate";
main(List<String> args) {
Isolate.spawn(foo, "Hello Isolate");
}
void foo(info) {
print("新的isolate:$info");
}
複製代碼
可是在真實開發中,咱們不會只是簡單的開啓一個新的Isolate,而不關心它的運行結果:
import "dart:isolate";
main(List<String> args) async {
// 1.建立管道
ReceivePort receivePort= ReceivePort();
// 2.建立新的Isolate
Isolate isolate = await Isolate.spawn<SendPort>(foo, receivePort.sendPort);
// 3.監聽管道消息
receivePort.listen((data) {
print('Data:$data');
// 再也不使用時,咱們會關閉管道
receivePort.close();
// 須要將isolate殺死
isolate?.kill(priority: Isolate.immediate);
});
}
void foo(SendPort sendPort) {
sendPort.send("Hello World");
}
複製代碼
可是咱們上面的通訊變成了單向通訊,若是須要雙向通訊呢?
compute
函數,它內部封裝了Isolate的建立和雙向通訊;注意:下面的代碼不是dart的API,而是Flutter的API,因此只有在Flutter項目中才能運行
main(List<String> args) async {
int result = await compute(powerNum, 5);
print(result);
}
int powerNum(int num) {
return num * num;
}
複製代碼
備註:全部內容首發於公衆號,以後除了Flutter也會更新其餘技術文章,TypeScript、React、Node、uniapp、mpvue、數據結構與算法等等,也會更新一些本身的學習心得等,歡迎你們關注