在寫這篇文章之前,我一直在猶豫,要不要在這裏講解 Dart 的異步相關話題,因為這部分內容很容易讓初學者望而卻步。首先關於單線程和異步之間的關係,比較容易讓人迷惑,雖然我一定會用自己的方式儘可能讓你聽懂。其次大量的異步操作方式(Future、await、async 等),目前你看不到具體的應用場景。(比如你學習過前端中的 Promise、await、async 可能會比較簡單,但是我會假設你沒有這樣的基礎)。
一. Dart 事件循環
1.1 什麼是事件循環
事件循環是什麼呢?
事件循環本身是單線程內的異步調度機制,得異步編程變的可能。其本質就是將需要處理的一系列事件(包括點擊事件、IO 事件、網絡事件等)放在一個事件隊列(Event Queue)或微任務隊列(Microtask Queue 微任務優先級高於事件隊列,會先被清空)中。然後不斷的從隊列中取出事件,並執行其對應的代碼塊,直到事件隊列清空。其事件處理流程遵循:執行同步代碼 -> 清空微任務隊列 -> 處理一個事件隊列任務 -> 重複前兩步(行成循環)。
我們來寫一個事件循環的偽代碼:
// 這裏我使用數組模擬隊列, 先進先出的原則
List eventQueue = [];
var event;
// 事件循環從啓動的一刻,永遠在執行
while (true) {
if (eventQueue.length > 0) {
// 取出一個事件
event = eventQueue.removeAt(0);
// 執行該事件
event();
}
}
當我們有一些事件時,比如點擊事件、IO事件、網絡事件時,它們就會被加入到 eventLoop 中,當發現事件隊列不為空時發現,就會取出事件,並且執行。
如下圖齒輪就是我們的事件循環,它會從隊列中依次取出事件來執行。

1.2 事件循環代碼解析
這裏我們來看一段偽代碼,理解點擊事件和網絡請求的事件是如何被執行的:
- 這是一段 Flutter 代碼,很多東西大家可能不是特別理解,但是耐心閲讀你會讀懂我們在做什麼。
- 一個按鈕 RaisedButton,當發生點擊時執行 onPressed 函數。
- onPressed 函數中,我們發送了一個網絡請求,請求成功後會執行 then 中的回調函數。
RaisedButton(
child: Text('Click me'),
onPressed: () {
final myFuture = http.get('https://example.com');
myFuture.then((response) {
if (response.statusCode == 200) {
print('Success!');
}
});
},
)
這些代碼是如何放在事件循環中執行呢?
- 當用户發生點擊的時候,onPressed 回調函數被放入事件循環中執行,執行的過程中發送了一個網絡請求。
- 網絡請求發出去後,該事件循環不會被阻塞,而是發現要執行的onPressed 函數已經結束,會將它丟棄掉。
- 網絡請求成功後,會執行 then 中傳入的回調函數,這也是一個事件,該事件被放入到事件循環中執行,執行完畢後,事件循環將其丟棄。
儘管 onPressed 和 then 中的回調有一些差異,但是它們對於事件循環來説,都是告訴它:我有一段代碼需要執行,快點幫我完成。
二. Dart 的異步模型
事件循環本身是單線程內的異步調度機制。
1.1. Dart 是單線程的
1.1.1. 程序中的耗時操作
開發中的耗時操作:
- 在開發中,我們經常會遇到一些耗時的操作需要完成,比如網絡請求、文件讀取等等;
- 如果我們的主線程一直在等待這些耗時的操作完成,那麼就會進行阻塞,無法響應其它事件,比如用户的點擊;
- 顯然,我們不能這麼幹!!
如何處理耗時的操作呢?
- 針對如何處理耗時的操作,不同的語言有不同的處理方式。
- 處理方式一: 多線程,比如 Java、C++,我們普遍的做法是開啓一個新的線程(Thread),在新的線程中完成這些異步的操作,再通過線程間通信的方式,將拿到的數據傳遞給主線程。
- 處理方式二: 單線程+事件循環,比如 JavaScript、Dart 都是基於單線程加事件循環來完成耗時操作的處理。不過單線程如何能進行耗時的操作呢?
1.1.2. 單線程的異步操作
我之前碰到很多開發者都對單線程的異步操作充滿了問號???

其實它們並不衝突:
- 因為我們的一個應用程序大部分時間都是處於空閒的狀態的,並不是無限制的在和用户進行交互。
- 比如等待用户點擊、網絡請求數據的返回、文件讀寫的 IO 操作,這些等待的行為並不會阻塞我們的線程;
- 這是因為類似於網絡請求、文件讀寫的 IO,我們都可以基於非阻塞調用;
阻塞式調用和非阻塞式調用
如果想搞懂這個點,我們需要知道操作系統中的阻塞式調用和非阻塞式調用的概念。
- 阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態。
- 阻塞式調用: 調用結果返回之前,當前線程會被掛起,調用線程只有在得到調用結果之後才會繼續執行。
- 非阻塞式調用: 調用執行之後,當前線程不會停止執行,只需要過一段時間來檢查一下有沒有結果返回即可。
我們用一個生活中的例子來模擬:
- 你中午餓了,需要點一份外賣,點外賣的動作就是我們的調用,拿到最後點的外賣就是我們要等待的結果。
- 阻塞式調用: 點了外賣,不再做任何事情,就是在傻傻的等待,你的線程停止了任何其他的工作。
- 非阻塞式調用: 點了外賣,繼續做其他事情:繼續工作、打把遊戲,你的線程沒有繼續執行其他事情,只需要偶爾去看一下有沒有人敲門,外賣有沒有送到即可。
而我們開發中的很多耗時操作,都可以基於這樣的 非阻塞式調用:
- 比如網絡請求本身使用了 Socket 通信,而 Socket 本身提供了 select 模型,可以進行
非阻塞方式的工作; - 比如文件讀寫的 IO 操作,我們可以使用操作系統提供的基於事件的回調機制;
這些操作都不會阻塞我們單線程的繼續執行,我們的線程在等待的過程中可以繼續去做別的事情:喝杯咖啡、打把遊戲,等真正有了響應,再去進行對應的處理即可。
這時,我們可能有兩個問題:
- 問題一: 如果在多核 CPU 中,單線程是不是就沒有充分利用 CPU 呢?這個問題,我會放在後面來講解。
- 問題二: 單線程是如何來處理網絡通信、IO 操作它們返回的結果呢?答案就是事件循環(Event Loop)。
1.2 Dart 的異步操作
Dart 中的異步操作主要使用 Future 以及 async、await。如果你之前有過前端的ES6、ES7 編程經驗,那麼完全可以將 Future 理解成 Promise,async、await 和 ES7 中基本一致。但是如果沒有前端開發經驗,Future 以及 async、await 如何理解呢?
1.2.1 認識 Future
Future:表示一個異步操作的最終結果(成功或失敗)。其次 Dart 通過 Future 將耗時任務包裝成非阻塞的異步任務來處理(如網絡請求、文件讀寫),避免阻塞主線程,即對異步的操作進行封裝。Future 的雙重視角:(pending→resolved/rejected)
從結果視角:Future 是一個“盒子”,它最終會被填入異步操作的成功值或錯誤。你可以通過 then 和 catchError 訪問這個結果。
從任務視角:Future 是一個“任務調度器”,它允許你將耗時任務包裝成非阻塞操作,由 Dart 事件循環自動調度執行,避免阻塞主線程。
1)同步的網絡請求
我們先來看一個例子吧:
- 在這個例子中,我使用 getNetworkData 來模擬了一個網絡請求;
- 該網絡請求需要3秒鐘的時間,之後返回數據;
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";
}
這段代碼會運行怎麼的結果呢?
- getNetworkData 會阻塞 main 函數的執行
main function start
// 等待3秒
network data
main function end
顯然,上面的代碼不是我們想要的執行效果,因為網絡請求阻塞了 main 函數,那麼意味着其後所有的代碼都無法正常的繼續執行。
2) 異步的網絡請求
我們來對我們上面的代碼進行改進,代碼如下:
和剛才的代碼唯一的區別在於我使用了 Future 對象來將耗時的操作放在了其中傳入的函數中;稍後,我們會講解它具體的一些 API,我們就暫時知道我創建了一個 Future 實例即可;
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";
});
}
我們來看一下代碼的運行結果:
- 這一次的代碼順序執行,沒有出現任何的阻塞現象;
- 和之前直接打印結果不同,這次我們打印了一個Future實例;
- 結論:我們將一個耗時的操作隔離了起來,這個操作不會再影響我們的主線程執行了。
- 問題:我們如何去拿到最終的結果呢?
main function start
Instance of 'Future<String>'
main function end
有了 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: 網絡請求出現錯誤
3)Future 使用補充
補充一:上面案例的小結
我們通過一個案例來學習了一些 Future 的使用過程:
- 創建一個 Future(可能是我們創建的,也可能是調用內部 API 或者第三方 API 獲取到的一個 Future,總之你需要獲取到一個 Future 實例,Future 通常會對一些異步的操作進行封裝);
- 通過 .then(成功回調函數)的方式來監聽 Future 內部執行完成時獲取到的結果;
- 通過 .catchError(失敗或異常回調函數)的方式來監聽 Future 內部執行失敗或者出現異常時的錯誤信息;
補充二:Future 的兩種狀態
事實上 Future 在執行的整個過程中,我們通常把它劃分成了兩種狀態:
狀態一:未完成狀態(uncompleted)
- 執行 Future 內部的操作時(在上面的案例中就是具體的網絡請求過程,我們使用了延遲來模擬),我們稱這個過程為未完成狀態。
狀態二:完成狀態(completed)
- 當 Future 內部的操作執行完成,通常會返回一個值,或者拋出一個異常。
- 這兩種情況,我們都稱 Future 為完成狀態。
Dart 官網有對這兩種狀態解析,之所以貼出來是區別於 Promise 的三種狀態
補充三:Future 的鏈式調用
上面代碼我們可以進行如下的改進:我們可以在 then 中繼續返回值,會在下一個鏈式的 then 調用回調函數中拿到返回的結果
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)
直接獲取一個完成的 Future,該 Future 會直接調用 then 的回調函數
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 中的 then 會作為新的任務會加入到事件隊列中(Event Queue),加入之後你肯定需要排隊執行了
Future.error(object)
直接獲取一個完成的 Future,但是是一個發生異常的 Future,該 Future 會直接調用 catchError 的回調函數
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(時間, 回調函數)
在延遲一定時間時執行回調函數,執行完回調函數後會執行 then 的回調;
之前的案例,我們也可以使用它來模擬,但是直接學習這個 API 會讓大家更加疑惑;
main(List<String> args) {
print("main function start");
Future.delayed(Duration(seconds: 3), () {
return "3秒後的信息";
}).then((value) {
print(value);
});
print("main function end");
}
1.2.2 await、async
1)理論概念理解
如果你已經完全搞懂了 Future,那麼學習 await、async 應該沒有什麼難度。
await、async 是什麼呢?
- 它們是 Dart 中的關鍵字(你這不是廢話嗎?廢話也還是要強調的,萬一你用它做變量名呢。)
- await:修飾函數,表示該函數包含異步操作,執行時會立即返回一個
Future對象。它們可以讓我們用同步的代碼格式,去實現異步的調用過程。 await:暫停異步函數的執行,直到Future完成(成功或拋出異常),然後繼續執行後續代碼。
我們已經知道,Future 可以做到不阻塞我們的線程,讓線程繼續執行,並且在完成某個操作時改變自己的狀態,並且回調 then 或者 errorCatch 回調。
如何生成一個 Future 呢?
- 通過我們前面學習的 Future 構造函數,或者後面學習的 Future 其他 API 都可以。
- 還有一種就是通過 async 的函數。
2)案例代碼演練
我們來對之前的 Future 異步處理代碼進行改造,改成 await、async 的形式。我們知道,如果直接這樣寫代碼,代碼是不能正常執行的:
- 因為 Future.delayed 返回的是一個 Future 對象,我們不能把它看成同步的返回數據:
"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;
}
修改後執行代碼,會看到如下的錯誤:
- 錯誤非常明顯:await 關鍵字必須存在於 async 函數中。
- 所以我們需要將
getNetworkData函數定義成 async 函數。
繼續修改代碼如下:
- 也非常簡單,只需要在函數的()後面加上一個async關鍵字就可以了
String getNetworkData() async {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "請求到的數據:" + result;
}
運行代碼,依然報錯(心想:你妹啊):
- 錯誤非常明顯:使用 async 標記的函數,必須返回一個 Future 對象。
- 所以我們需要繼續修改代碼,將返回值寫成一個 Future。
繼續修改代碼如下:
Future<String> getNetworkData() async {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "請求到的數據:" + result;
}
這段代碼應該是我們理想當中執行的代碼了
- 我們現在可以像同步代碼一樣去使用 Future 異步返回的結果;
- 等待拿到結果之後和其他數據進行拼接,然後一起返回;
- 返回的時候並不需要包裝一個 Future,直接返回即可,但是返回值會默認被包裝在一個 Future 中;
3)讀取 json 案例
我這裏給出了一個在 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;
}
1.2.3 Stream
在 Flutter 中,異步編程是非常重要的一部分,特別是在處理用户輸入、網絡請求或其他涉及時間的操作時。Flutter 提供了一種強大的工具,稱為 Stream,用於簡化異步編程的過程。
1)什麼是 Stream?
Stream 是一種用於處理異步數據的流式 API。它可以用於處理一系列事件,例如用户輸入、網絡請求的響應、定時器觸發等。通過使用 Stream,我們能夠更加輕鬆地管理和響應這些異步事件。
在 Flutter 中,Stream 由兩個主要部分組成:流本身和監聽器。流是事件序列的源頭,而監聽器則監聽並在新事件到達時做出響應。
創建 Stream:可以使用 StreamController 類。以下是一個簡單的例子:
import 'dart:async';
void main() {
var controller = StreamController<String>();
var stream = controller.stream;
stream.listen((data) {
print('Received data: $data');
});
controller.add('Hello');
controller.add('World');
controller.close();
}
在上面的例子中,我們創建了一個 StreamController 並通過其 stream 屬性獲得了一個 Stream。然後,我們通過調用 listen 方法來監聽 Stream 上的事件。最後,我們使用 add 方法向 Stream 中添加了兩個事件,並通過 close 方法關閉了 Stream。
2)用户輸入示例:實時搜索框
場景説明:
- 用户輸入時觸發
TextField的onChanged回調; - 通過
_controller.add()將輸入值注入 Stream; StreamBuilder監聽 Stream 並實時更新 UI,實現無延遲的實時搜索效果;
import 'dart:async';
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
@override
_SearchPageState createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
final _controller = StreamController<String>();
String _searchResult = '';
@override
void dispose() {
_controller.close(); // 必須關閉Stream避免內存泄漏
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
TextField(
onChanged: (value) {
//用户輸入時通過 StreamController添加事件
_controller.add(value);
},
decoration: InputDecoration(labelText: '輸入關鍵詞'),
),
StreamBuilder<String>(
stream: _controller.stream,
builder: (context, snapshot) {
// 根據 Stream 數據更新 UI
if (snapshot.hasData) {
_searchResult = snapshot.data!;
return Text('搜索結果: $_searchResult');
}
return Text('等待輸入...');
},
),
],
),
),
);
}
}
3)網絡請求示例:分頁加載數據
場景説明:
- 使用
async*生成器創建異步 Stream,模擬分頁 API 請求; - 每次
yield返回一頁數據,實現流式數據加載; - 結合
StreamBuilder自動處理加載狀態、錯誤處理和數據渲染;
import 'dart:async';
import 'package:http/http.dart' as http;
// 模擬網絡請求的分頁數據獲取
Stream<List<String>> fetchPaginatedData(int pageSize) async* {
int page = 0;
while (true) {
final response = await http.get(
Uri.parse('https://api.example.com/data?page=$page&size=$pageSize')
);
if (response.statusCode == 200) {
final List<String> data = parseResponse(response.body);
if (data.isEmpty) break; // 無更多數據時終止Stream
yield data; // 每次yield生成一個數據塊
page++;
} else {
throw Exception('請求失敗');
}
}
}
// 使用StreamBuilder實現分頁加載
StreamBuilder<List<String>>(
stream: fetchPaginatedData(20),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) => ListTile(title: Text(snapshot.data![index])),
);
} else if (snapshot.hasError) {
return Text('加載失敗');
}
return CircularProgressIndicator();
},
)
1.2.4 StreamBuilder
Flutter 中的 StreamBuilder 是一個非常方便的小部件,它可以根據 Stream 的事件動態重構界面。以下是一個簡單的例子:
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final StreamController<String> _controller = StreamController<String>();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('StreamBuilder Example'),
),
body: StreamBuilder<String>(
stream: _controller.stream,
builder: (context, snapshot) {
return Center(
child: Text(snapshot.data ?? 'No data'),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller.add('New data');
},
child: Icon(Icons.add),
),
),
);
}
}
在上面的例子中,StreamBuilder 根據 Stream 中的數據動態更新了界面上的文本。當點擊 FloatingActionButton 時,會向 Stream 中添加新的數據,StreamBuilder 會立即更新 UI。
除了上述基本用法外,Stream 還有許多其他強大的功能和用途,例如錯誤處理、廣播事件等。通過深入學習 Stream 的高級特性,您可以更好地利用 Flutter 中的異步編程。
總的來説,Flutter 中的 Stream 是一個強大而靈活的工具,它使得異步編程變得更加容易和直觀。通過合理使用 Stream,您可以更好地處理應用中的異步操作,提高用户體驗。
三. 任務執行順序
3.1 認識微任務隊列
在前面學習學習中,我們知道 Dart 中有一個事件循環(Event Loop)來執行我們的代碼,裏面存在一個事件隊列(Event Queue),事件循環不斷從事件隊列中取出事件執行。
但是如果我們嚴格來劃分的話,在 Dart 中還存在另一個隊列:微任務隊列(Microtask Queue)。
- 微任務隊列的優先級要高於事件隊列;
- 也就是説事件循環都是優先執行微任務隊列中的任務,再執行事件隊列中的任務;
那麼在 Flutter 開發中,哪些是放在事件隊列,哪些是放在微任務隊列呢?
- 所有的外部事件任務都在事件隊列中,如 IO、計時器、點擊、以及繪製事件等;
- 而微任務通常來源於 Dart 內部,並且微任務非常少。這是因為如果微任務非常多,就會造成事件隊列排不上隊,會阻塞任務隊列的執行(比如用户點擊沒有反應的情況);
説道這裏,你可能已經有點凌亂了,在 Dart 的單線程中,代碼到底是怎樣執行的呢?
- Dart 的入口是 main 函數,所以 main 函數中的代碼會優先執行;
- main 函數執行完後,會啓動一個事件循環(Event Loop),啓動後開始執行隊列中的任務;
- 首先,會按照先進先出的順序,執行微任務隊列(Microtask Queue)中的所有任務;
- 其次,會按照先進先出的順序,執行事件隊列(Event Queue)中的所有任務;

3.2 如何創建微任務
在開發中,我們可以通過 Dart 中 async 下的 scheduleMicrotask 來創建一個微任務:
import "dart:async";
main(List<String> args) {
scheduleMicrotask(() {
print("Hello Microtask");
});
}
在開發中,如果我們有一個任務不希望它放在 Event Queue 中依次排隊,那麼就可以創建一個微任務了。
Future 的代碼是加入到事件隊列還是微任務隊列呢?
Future 中通常有兩個函數執行體:
- Future 構造函數傳入的函數體;
- then 的函數體(catchError 等同看待);
那麼它們是加入到什麼隊列中的呢?
-
Future 構造函數中的函數體加入事件隊列中,按聲明順序執行;
-
then 的函數體要分成三種情況:
-
情況一:Future 沒有執行完成(如耗時操作未結束),那麼 then 回調會被加入事件隊列,與 Future 的執行體按順序執行;
Future(() => print('f1')).then(() => print('then1')); // 執行順序:f1 → then1(均在事件隊列) -
情況二:如果 Future 執行完後調用 then,如已返回結果或拋出錯誤後調用 then,then 回調會被加入微任務隊列,優先於事件隊列執行;
Future f = Future.value('done'); f.then((v) => print('then2')); // 微任務隊列 print('main end'); // 輸出:main end → then2(微任務優先) -
情況三:鏈式 then 回調按順序執行,實際是在事件循環中依次處理。每個 then 回調必須等待前一個完成(包括其內部異步操作);
Future.value(1) .then((v) => v + 2) // 同步操作 .then((v) => Future.value(v + 3)) // 異步操作,加入事件隊列 .then((v) => print(v)); // 等待前一個Future完成 // 執行順序:1 → 3 → 6
3.3 代碼執行順序
我們根據前面的規則來看下面代碼執行順序案例:
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
代碼分析:
- 1、main 函數先執行,所以
main start和main end先執行,沒有任何問題; - 2、main 函數執行過程中,會將一些任務分別加入到
EventQueue和MicrotaskQueue中; - 3、task7 通過
scheduleMicrotask函數調用,所以它被最早加入到MicrotaskQueue,會被先執行; - 4、然後開始執行
EventQueue,task1 被添加到EventQueue中被執行; - 5、通過
final future = Future(() => null);創建的 future 的 then 被添加到微任務中,微任務直接被優先執行,所以會執行 task6; - 6、依次在
EventQueue中添加 task2、task3、task5 被執行; - 7、task3 的打印執行完後,調用
scheduleMicrotask,那麼在執行完這次的EventQueue後會執行,所以在 task5 後執行 task4(注意:scheduleMicrotask的調用是作為 task3 的一部分代碼,所以 task4 是要在 task5 之後執行的) - 8、task8、task9、task10 依次添加到
EventQueue被執行;
事實上,上面的代碼執行順序有可能出現在面試中,我們開發中通常不會出現這種複雜的嵌套,並且需要完全搞清楚它的執行順序;
但是,瞭解上面的代碼執行順序,會讓你對 EventQueue 和 microtaskQueue 有更加深刻的理解。
四. 多核 CPU 的利用
4.1 隔離 Isolate 機制的理解
在 Dart 中,有一個隔離 Isolate 機制,它是什麼呢?
Isolate 是一種輕量級的併發執行單元,可以在不同的線程中運行代碼。每個 Isolate 都有自己的內存空間、Event Loop 與 Queue。Isolate 之間不共享任何資源,只能依靠消息機制通信,因此也就沒有資源搶佔問題。在 Dart 中本身是沒有多進程概念的,但可以使用 Isolate 隔離機制來實現多進程效果。
比如 Flutter 中就有一個 Root Isolate,負責運行 Flutter 的代碼,比如 UI 渲染、用户交互等等;但是,如果只有一個 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");
}
4.2 Isolate 通信機制
但是在真實開發中,我們不會只是簡單的開啓一個新的 Isolate,而不關心它的運行結果:
- 我們需要新的 Isolate 進行計算,並且將計算結果告知 Main Isolate(也就是默認開啓的 Isolate);
- Isolate 通過發送管道(SendPort/ReceivePort)實現消息通信機制;
- 我們可以在啓動併發 Isolate 時將 Main Isolate 的發送管道作為參數傳遞給它;
- 併發在執行完畢時,可以利用這個管道給 Main 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");
}
但是我們上面的通信變成了單向通信,如果需要雙向通信呢?Isolate 隔離-雙向通訊:這裏主要通過 Isolate 機制構建了一個主線程和一個子線程並進行雙向通信,主要使用 SendPort 和 ReceivePort。代碼實現如下:
import 'dart:isolate';
var anotherIsolate;
void startMainIsolate() async {
var receivePort = ReceivePort();
var sendPort;
anotherIsolate = await Isolate.spawn(threadIsolateInit, receivePort.sendPort);
receivePort.listen((date) {
if (date is SendPort) {
sendPort = date;
print("雙向通訊建立成功");
return;
}
print("主線程 接收消息:data = $date");
sendPort.send("XXXXX");
});
}
void threadIsolateInit(SendPort sendPort) async {
var receivePort = ReceivePort();
print("子線程 接受到來自 主線程的port,嘗試建立同主線程的雙向通訊");
receivePort.listen((date) {
print("子線程接收消息:data = $date");
});
sendPort.send(receivePort.sendPort);
for (var index = 0; index < 10; index++) {
sendPort.send("子線程 發送消息:$index");
}
}
void main() {
startMainIsolate();
}
4.3 compute 封裝
Flutter 提供了支持併發計算的 compute 函數,它內部封裝了 Isolate 的創建和雙向通信;利用它我們可以充分利用多核心 CPU,並且使用起來也非常簡單;
注意:下面的代碼不是 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;
}