該文章屬于劉小壯原創(chuàng)稚失,轉(zhuǎn)載請注明:劉小壯
Flutter
默認(rèn)是單線程任務(wù)處理的床佳,如果不開啟新的線程野舶,任務(wù)默認(rèn)在主線程中處理槐雾。
事件隊列
和iOS應(yīng)用很像妓笙,在Dart
的線程中也存在事件循環(huán)和消息隊列的概念若河,但在Dart
中線程叫做isolate
。應(yīng)用程序啟動后寞宫,開始執(zhí)行main
函數(shù)并運行main isolate
萧福。
每個isolate
包含一個事件循環(huán)以及兩個事件隊列,event loop
事件循環(huán)辈赋,以及event queue
和microtask queue
事件隊列鲫忍,event
和microtask
隊列有點類似iOS的source0
和source1
。
- event queue:負責(zé)處理I/O事件钥屈、繪制事件悟民、手勢事件、接收其他
isolate
消息等外部事件篷就。 - microtask queue:可以自己向
isolate
內(nèi)部添加事件射亏,事件的優(yōu)先級比event queue
高。
這兩個隊列也是有優(yōu)先級的竭业,當(dāng)isolate
開始執(zhí)行后智润,會先處理microtask
的事件,當(dāng)microtask
隊列中沒有事件后未辆,才會處理event
隊列中的事件窟绷,并按照這個順序反復(fù)執(zhí)行。但需要注意的是咐柜,當(dāng)執(zhí)行microtask
事件時钾麸,會阻塞event
隊列的事件執(zhí)行,這樣就會導(dǎo)致渲染炕桨、手勢響應(yīng)等event
事件響應(yīng)延時。為了保證渲染和手勢響應(yīng)肯腕,應(yīng)該盡量將耗時操作放在event
隊列中献宫。
async、await
在異步調(diào)用中有三個關(guān)鍵詞实撒,async
姊途、await
、Future
知态,其中async
和await
需要一起使用捷兰。在Dart
中可以通過async
和await
進行異步操作,async
表示開啟一個異步操作负敏,也可以返回一個Future
結(jié)果贡茅。如果沒有返回值,則默認(rèn)返回一個返回值為null
的Future
。
async
顶考、await
本質(zhì)上就是Dart
對異步操作的一個語法糖赁还,可以減少異步調(diào)用的嵌套調(diào)用,并且由async
修飾后返回一個Future
驹沿,外界可以以鏈?zhǔn)秸{(diào)用的方式調(diào)用艘策。這個語法是JS
的ES7
標(biāo)準(zhǔn)中推出的,Dart
的設(shè)計和JS
相同渊季。
下面封裝了一個網(wǎng)絡(luò)請求的異步操作朋蔫,并且將請求后的Response
類型的Future
返回給外界,外界可以通過await
調(diào)用這個請求却汉,并獲取返回數(shù)據(jù)驯妄。從代碼中可以看到,即便直接返回一個字符串病涨,Dart
也會對其進行包裝并成為一個Future
富玷。
Future<Response> dataReqeust() async {
String requestURL = 'https://jsonplaceholder.typicode.com/posts';
Client client = Client();
Future<Response> response = client.get(requestURL);
return response;
}
Future<String> loadData() async {
Response response = await dataReqeust();
return response.body;
}
在代碼示例中,執(zhí)行到loadData
方法時既穆,會同步進入方法內(nèi)部進行執(zhí)行赎懦,當(dāng)執(zhí)行到await
時就會停止async
內(nèi)部的執(zhí)行,從而繼續(xù)執(zhí)行外面的代碼幻工。當(dāng)await
有返回后励两,會繼續(xù)從await
的位置繼續(xù)執(zhí)行。所以await
的操作囊颅,不會影響后面代碼的執(zhí)行当悔。
下面是一個代碼示例,通過async
開啟一個異步操作踢代,通過await
等待請求或其他操作的執(zhí)行盲憎,并接收返回值。當(dāng)數(shù)據(jù)發(fā)生改變時胳挎,調(diào)用setState
方法并更新數(shù)據(jù)源饼疙,Flutter
會更新對應(yīng)的Widget
節(jié)點視圖。
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
Future
Future
就是延時操作的一個封裝慕爬,可以將異步任務(wù)封裝為Future
對象窑眯。獲取到Future
對象后,最簡單的方法就是用await
修飾医窿,并等待返回結(jié)果繼續(xù)向下執(zhí)行磅甩。正如上面async、await
中講到的姥卢,使用await
修飾時需要配合async
一起使用卷要。
在Dart
中渣聚,和時間相關(guān)的操作基本都和Future
有關(guān),例如延時操作却妨、異步操作等饵逐。下面是一個很簡單的延時操作,通過Future
的delayed
方法實現(xiàn)彪标。
loadData() {
// DateTime.now()倍权,獲取當(dāng)前時間
DateTime now = DateTime.now();
print('request begin $now');
Future.delayed(Duration(seconds: 1), (){
now = DateTime.now();
print('request response $now');
});
}
Dart
還支持對Future
的鏈?zhǔn)秸{(diào)用,通過追加一個或多個then
方法來實現(xiàn)捞烟,這個特性非常實用薄声。例如一個延時操作完成后,會調(diào)用then
方法题画,并且可以傳遞一個參數(shù)給then
默辨。調(diào)用方式是鏈?zhǔn)秸{(diào)用,也就代表可以進行很多層的處理苍息。這有點類似于iOS的RAC
框架缩幸,鏈?zhǔn)秸{(diào)用進行信號處理。
Future.delayed(Duration(seconds: 1), (){
int age = 18;
return age;
}).then((onValue){
onValue++;
print('age $onValue');
});
協(xié)程
如果想要了解async
竞思、await
的原理表谊,就要先了解協(xié)程的概念,async
盖喷、await
本質(zhì)上就是協(xié)程的一種語法糖爆办。協(xié)程,也叫作coroutine
课梳,是一種比線程更小的單元距辆。如果從單元大小來說,基本可以理解為進程->線程->協(xié)程暮刃。
任務(wù)調(diào)度
在弄懂協(xié)程之前跨算,首先要明白并發(fā)和并行的概念,并發(fā)指的是由系統(tǒng)來管理多個IO的切換椭懊,并交由CPU去處理漂彤。并行指的是多核CPU在同一時間里執(zhí)行多個任務(wù)。
并發(fā)的實現(xiàn)由非阻塞操作+事件通知來完成灾搏,事件通知也叫做“中斷”。操作過程分為兩種立润,一種是CPU對IO進行操作狂窑,在操作完成后發(fā)起中斷告訴IO操作完成。另一種是IO發(fā)起中斷桑腮,告訴CPU可以進行操作泉哈。
線程本質(zhì)上也是依賴于中斷來進行調(diào)度的,線程還有一種叫做“阻塞式中斷”,就是在執(zhí)行IO操作時將線程阻塞丛晦,等待執(zhí)行完成后再繼續(xù)執(zhí)行奕纫。但線程的消耗是很大的,并不適合大量并發(fā)操作的處理烫沙,而通過單線程并發(fā)可以進行大量并發(fā)操作匹层。當(dāng)多核CPU出現(xiàn)后,單個線程就無法很好的利用多核CPU的優(yōu)勢了锌蓄,所以又引入了線程池的概念升筏,通過線程池來管理大量線程。
協(xié)程
在程序執(zhí)行過程中瘸爽,離開當(dāng)前的調(diào)用位置有兩種方式您访,繼續(xù)調(diào)用其他函數(shù)和return
返回離開當(dāng)前函數(shù)。但是執(zhí)行return
時剪决,當(dāng)前函數(shù)在調(diào)用棧中的局部變量灵汪、形參等狀態(tài)則會被銷毀。
協(xié)程分為無線協(xié)程和有線協(xié)程柑潦,無線協(xié)程在離開當(dāng)前調(diào)用位置時享言,會將當(dāng)前變量放在堆區(qū),當(dāng)再次回到當(dāng)前位置時妒茬,還會繼續(xù)從堆區(qū)中獲取到變量担锤。所以,一般在執(zhí)行當(dāng)前函數(shù)時就會將變量直接分配到堆區(qū)乍钻,而async
肛循、await
就屬于無線協(xié)程的一種。有線協(xié)程則會將變量繼續(xù)保存在棧區(qū)银择,在回到指針指向的離開位置時多糠,會繼續(xù)從棧中取出調(diào)用。
async浩考、await原理
以async
夹孔、await
為例,協(xié)程在執(zhí)行時析孽,執(zhí)行到async
則表示進入一個協(xié)程搭伤,會同步執(zhí)行async
的代碼塊。async
的代碼塊本質(zhì)上也相當(dāng)于一個函數(shù)袜瞬,并且有自己的上下文環(huán)境怜俐。當(dāng)執(zhí)行到await
時,則表示有任務(wù)需要等待邓尤,CPU則去調(diào)度執(zhí)行其他IO拍鲤,也就是后面的代碼或其他協(xié)程代碼贴谎。過一段時間CPU就會輪訓(xùn)一次,看某個協(xié)程是否任務(wù)已經(jīng)處理完成季稳,有返回結(jié)果可以被繼續(xù)執(zhí)行擅这,如果可以被繼續(xù)執(zhí)行的話,則會沿著上次離開時指針指向的位置繼續(xù)執(zhí)行景鼠,也就是await
標(biāo)志的位置仲翎。
由于并沒有開啟新的線程,只是進行IO中斷改變CPU調(diào)度莲蜘,所以網(wǎng)絡(luò)請求這樣的異步操作可以使用async
谭确、await
,但如果是執(zhí)行大量耗時同步操作的話票渠,應(yīng)該使用isolate
開辟新的線程去執(zhí)行逐哈。
如果用協(xié)程和iOS的dispatch_async
進行對比,可以發(fā)現(xiàn)二者是比較相似的问顷。從結(jié)構(gòu)定義來看昂秃,協(xié)程需要將當(dāng)前await
的代碼塊相關(guān)的變量進行存儲,dispatch_async
也可以通過block
來實現(xiàn)臨時變量的存儲能力杜窄。
我之前還在想一個問題肠骆,蘋果為什么不引入?yún)f(xié)程的特性呢?后來想了一下塞耕,await
和dispatch_async
都可以簡單理解為異步操作蚀腿,OC的線程是基于Runloop
實現(xiàn)的,Dart
本質(zhì)上也是有事件循環(huán)的扫外,而且二者都有自己的事件隊列莉钙,只是隊列數(shù)量和分類不同。
我覺得當(dāng)執(zhí)行到await
時筛谚,保存當(dāng)前的上下文磁玉,并將當(dāng)前位置標(biāo)記為待處理任務(wù)疾呻,用一個指針指向當(dāng)前位置绊起,并將待處理任務(wù)放入當(dāng)前isolate
的隊列中。在每個事件循環(huán)時都去詢問這個任務(wù)援雇,如果需要進行處理吮铭,就恢復(fù)上下文進行任務(wù)處理时迫。
Promise
這里想提一下JS
里的Promise
語法,在iOS中會出現(xiàn)很多if
判斷或者其他的嵌套調(diào)用谓晌,而Promise
可以把之前橫向的嵌套調(diào)用别垮,改成縱向鏈?zhǔn)秸{(diào)用。如果能把Promise
引入到OC里扎谎,可以讓代碼看起來更簡潔碳想,直觀。
isolate
isolate
是Dart
平臺對線程的實現(xiàn)方案毁靶,但和普通Thread
不同的是胧奔,isolate
擁有獨立的內(nèi)存,isolate
由線程和獨立內(nèi)存構(gòu)成预吆。正是由于isolate
線程之間的內(nèi)存不共享龙填,所以isolate
線程之間并不存在資源搶奪的問題,所以也不需要鎖拐叉。
通過isolate
可以很好的利用多核CPU岩遗,來進行大量耗時任務(wù)的處理。isolate
線程之間的通信主要通過port
來進行凤瘦,這個port
消息傳遞的過程是異步的宿礁。通過Dart
源碼也可以看出,實例化一個isolate
的過程包括蔬芥,實例化isolate
結(jié)構(gòu)體梆靖、在堆中分配線程內(nèi)存、配置port
等過程笔诵。
isolate
看起來其實和進程比較相似返吻,之前請教阿里架構(gòu)師宗心問題時,宗心也說過“isolate
的整體模型我自己的理解其實更像進程乎婿,而async
测僵、await
更像是線程”。如果對比一下isolate
和進程的定義谢翎,會發(fā)現(xiàn)確實isolate
很像是進程捍靠。
代碼示例
下面是一個isolate
的例子,例子中新創(chuàng)建了一個isolate
岳服,并且綁定了一個方法進行網(wǎng)絡(luò)請求和數(shù)據(jù)解析的處理剂公,并通過port
將處理好的數(shù)據(jù)返回給調(diào)用方。
loadData() async {
// 通過spawn新建一個isolate吊宋,并綁定靜態(tài)方法
ReceivePort receivePort =ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// 獲取新isolate的監(jiān)聽port
SendPort sendPort = await receivePort.first;
// 調(diào)用sendReceive自定義方法
List dataList = await sendReceive(sendPort, 'https://jsonplaceholder.typicode.com/posts');
print('dataList $dataList');
}
// isolate的綁定方法
static dataLoader(SendPort sendPort) async{
// 創(chuàng)建監(jiān)聽port纲辽,并將sendPort傳給外界用來調(diào)用
ReceivePort receivePort =ReceivePort();
sendPort.send(receivePort.sendPort);
// 監(jiān)聽外界調(diào)用
await for (var msg in receivePort) {
String requestURL =msg[0];
SendPort callbackPort =msg[1];
Client client = Client();
Response response = await client.get(requestURL);
List dataList = json.decode(response.body);
// 回調(diào)返回值給調(diào)用者
callbackPort.send(dataList);
}
}
// 創(chuàng)建自己的監(jiān)聽port,并且向新isolate發(fā)送消息
Future sendReceive(SendPort sendPort, String url) {
ReceivePort receivePort =ReceivePort();
sendPort.send([url, receivePort.sendPort]);
// 接收到返回值璃搜,返回給調(diào)用者
return receivePort.first;
}
isolate
和iOS中的線程還不太一樣拖吼,isolate
的線程更偏底層。當(dāng)生成一個isolate
后这吻,其內(nèi)存是各自獨立的吊档,相互之間并不能進行訪問。但isolate
提供了基于port
的消息機制唾糯,通過建立通信雙方的sendPort
和receiveport
怠硼,進行相互的消息傳遞鬼贱,在Dart
中叫做消息傳遞。
從上面例子中可以看出香璃,在進行isolate
消息傳遞的過程中这难,本質(zhì)上就是進行port
的傳遞。將port
傳遞給其他isolate
葡秒,其他isolate
通過port
拿到sendPort
姻乓,向調(diào)用方發(fā)送消息來進行相互的消息傳遞。
Embedder
正如其名眯牧,Embedder
是一個嵌入層蹋岩,將Flutter
嵌入到各個平臺上。Embedder
負責(zé)范圍包括原生平臺插件学少、線程管理剪个、事件循環(huán)等。
Embedder
中存在四個Runner
旱易,四個Runner
分別如下禁偎。其中每個Flutter Engine
各自對應(yīng)一個UI Runner
、GPU Runner
阀坏、IO Runner
如暖,但所有Engine
共享一個Platform Runner
。
Runner
和isolate
并不是一碼事忌堂,彼此相互獨立盒至。以iOS平臺為例,Runner
的實現(xiàn)就是CFRunLoop
士修,以一個事件循環(huán)的方式不斷處理任務(wù)枷遂。并且Runner
不只處理Engine
的任務(wù),還有Native Plugin
帶來的原生平臺的任務(wù)棋嘲。而isolate
則由Dart VM
進行管理酒唉,和原生平臺線程并無關(guān)系。
Platform Runner
Platform Runner
和iOS平臺的Main Thread
非常相似沸移,在Flutter
中除耗時操作外痪伦,所有任務(wù)都應(yīng)該放在Platform
中,Flutter
中的很多API并不是線程安全的雹锣,放在其他線程中可能會導(dǎo)致一些bug网沾。
但例如IO之類的耗時操作,應(yīng)該放在其他線程中完成蕊爵,否則會影響Platform
的正常執(zhí)行辉哥,甚至于被watchdog
干掉。但需要注意的是,由于Embedder Runner
的機制醋旦,Platform
被阻塞后并不會導(dǎo)致頁面卡頓恒水。
不只是Flutter Engine
的代碼在Platform
中執(zhí)行,Native Plugin
的任務(wù)也會派發(fā)到Platform
中執(zhí)行饲齐。實際上寇窑,在原生側(cè)的代碼運行在Platform Runner
中,而Flutter
側(cè)的代碼運行在Root Isolate
中箩张,如果在Platform
中執(zhí)行耗時代碼,則會卡原生平臺的主線程窗市。
UI Runner
UI Runner
負責(zé)為Flutter Engine
執(zhí)行Root Isolate
的代碼先慷,除此之外,也處理來自Native Plugin
的任務(wù)咨察。Root Isolate
為了處理自身事件论熙,綁定了很多函數(shù)方法。程序啟動時摄狱,Flutter Engine
會為Root
綁定UI Runner
的處理函數(shù)脓诡,使Root Isolate
具備提交渲染幀的能力。
當(dāng)Root Isolate
向Engine
提交一次渲染幀時媒役,Engine
會等待下次vsync祝谚,當(dāng)下次vsync到來時,由Root Isolate
對Widgets
進行布局操作酣衷,并生成頁面的顯示信息的描述交惯,并將信息交給Engine
去處理。
由于對widgets
進行layout
并生成layer tree
是UI Runner
進行的穿仪,如果在UI Runner
中進行大量耗時處理席爽,會影響頁面的顯示,所以應(yīng)該將耗時操作交給其他isolate
處理啊片,例如來自Native Plugin
的事件只锻。
GPU Runner
GPU Runner
并不直接負責(zé)渲染操作,其負責(zé)GPU相關(guān)的管理和調(diào)度紫谷。當(dāng)layer tree
信息到來時齐饮,GPU Runner
將其提交給指定的渲染平臺,渲染平臺是Skia配置的碴里,不同平臺可能有不同的實現(xiàn)沈矿。
GPU Runner
相對比較獨立,除了Embedder
外其他線程均不可向其提交渲染信息咬腋。
IO Runner
一些GPU Runner
中比較耗時的操作羹膳,就放在IO Runner
中進行處理,例如圖片讀取根竿、解壓陵像、渲染等操作就珠。但是只有GPU Runner
才能對GPU提交渲染信息,為了保證IO Runner
也具備這個能力醒颖,所以IO Runner
會引用GPU Runner
的context
妻怎,這樣就具備向GPU提交渲染信息的能力。
簡書由于排版的問題泞歉,閱讀體驗并不好逼侦,布局、圖片顯示腰耙、代碼等很多問題榛丢。所以建議到我Github
上,下載Flutter編程指南 PDF
合集挺庞。把所有Flutter
文章總計三篇晰赞,都寫在這個PDF
中,而且左側(cè)有目錄选侨,方便閱讀掖鱼。
下載地址:Flutter編程指南 PDF
麻煩各位大佬點個贊,謝謝援制!??