咱們知道Flutter 框架有出色的渲染和交互能力。支撐起這些複雜的能力背後,其實是基於單線程模型的 Dart。那麼,與原生 Android 和 iOS 的多線程機制相比,單線程的 Dart 如何從語言設計層面和代碼運行機制上保證 Flutter UI 的流暢性呢?web
咱們從下面幾個方面闡述一下:編程
dart是單線程運行的。怎麼理解這句話呢, 從下面幾個方面能夠看到這個設計思想.bash
dart默認運行在Main函數存在線程,在dart中稱之爲isolate,這個線程咱們可稱之爲main isolate。單線程任務處理的,若是不開啓新的isolate,任務默認在主isolate中處理。一旦 Dart 函數執行,它將按照在 main 函數出現的次序一個接一個地持續執行,直到退出。換而言之,Dart 函數在執行期間,沒法被其餘 Dart 代碼打斷。網絡
Android和IOS能夠自由的開闢除了UI主線程以外的線程,這些線程和主線程能夠共享內存的變量,可是, Dart中的isolate沒法共享內存。Isolate 不能共享內存,他們就像是單獨的分離的 app,經過消息進行溝通。除了顯式指定代碼運行在別的 isolate 或者 worker 中,其餘代碼都運行在 app 的 main isolate 中。更多信息能夠訪問Use isolates or workers if necessary多線程
(1)假若有一個任務(讀寫文件或者網絡)耗時10秒,而且加入到了事件任務隊列中,執行單這個任務的時候不就把線程卡主嗎?併發
答:文件I/O和網絡調用並非在Dart層作的,而是由操做系統提供的異步線程,他倆把活兒幹完以後把結果剛到隊列中,Dart代碼只是執行一個簡單的讀動做。app
(2)單線程模型是指的事件隊列模型,和繪製界面的線程是一個嗎?框架
答:咱們所說的單線程指的是主Isolate。而GPU繪製指令有單獨的線程執行,跟主Isolate無關。事實上Flutter提供了4種task runner,有獨立的線程去運行專屬的任務:參見:深刻理解Flutter引擎線程模式異步
如圖所示,dart也存在事件隊列和事件循環。每一個isolate也包含一個事件循環,區別是他有兩個事件隊列,event loop事件循環,以及event queue和microtask queue事件隊列,event和microtask隊列有點相似iOS的source0和source1。async
MicroTask
隊列是否爲空,非空則先執行MicroTask
隊列中的MicroTaskMicroTask
執行完後,檢查有沒有下一個MicroTask
,直到MicroTask
隊列爲空,纔去執行Event
隊列Evnet
隊列取出一個事件處理完後,再次返回第一步,去檢查MicroTask
隊列是否爲空咱們能夠看出,將任務加入到MicroTask
中能夠被儘快執行,但也須要注意,當事件循環在處理MicroTask
隊列時,會阻塞event隊列的事件執行,這樣就會致使渲染、手勢響應等event事件響應延時。爲了保證渲染和手勢響應,應該儘可能將耗時操做放在event隊列中。
咱們一般不多會直接用到微任務隊列,就連 Flutter 內部,也只有 7 處用到了而已(好比,手勢識別、文本輸入、滾動視圖、保存頁面效果等須要高優執行任務的場景)。
簡單總結爲一二一模型:1個事件循環和2個隊列的單線程執行模型。
爲何單線程也能夠異步?這裏有一個大前提,那就是咱們的 App 絕大多數時間都在等待。好比,等用戶點擊、等網絡請求返回、等文件 IO 結果,等等。而這些等待行爲並非阻塞的。好比說,網絡請求,Socket 自己提供了 select 模型能夠異步查詢;而文件 IO,操做系統也提供了基於事件的回調機制。因此,基於這些特色,單線程模型能夠在等待的過程當中作別的事情,等真正須要響應結果了,再去作對應的處理。由於等待過程並非阻塞的,因此給咱們的感受就像是同時在作多件事情同樣。但其實始終只有一個線程在處理你的事情。
異步任務咱們用的最多的仍是優先級更低的 Event Queue。好比,I/O、繪製、定時器這些異步事件,都是經過事件隊列驅動主線程執行的。
Dart 爲 Event Queue 的任務創建提供了一層封裝,叫做 Future。Future 還提供了鏈式調用的能力,能夠在異步任務執行完畢後依次執行鏈路上的其餘函數體。
new Future((){
// doing something
});複製代碼
微任務是由 scheduleMicroTask 創建的。以下所示,這段代碼會在下一個事件循環中輸出一段字符串:
scheduleMicrotask(() => print('This is a microtask'));複製代碼
鏈式調用:
Future(() => print('Running in Future 1'));//下一個事件循環輸出字符串
Future(() => print(‘Running in Future 2')) .then((_) => print('and then 1')) .then((_) => print('and then 2’));//上一個事件循環結束後,連續輸出三段字符串複製代碼
Dart 會將異步任務的函數執行體放入事件隊列,而後當即返回,後續的代碼繼續同步執行。而當同步執行的代碼執行完畢後,事件隊列會按照加入事件隊列的順序(即聲明順序),依次取出事件,最後同步執行 Future 的函數體及後續的 then。這意味着,then 與 Future 函數體共用一個事件循環。而若是 Future 有多個 then,它們也會按照鏈式調用的前後順序同步執行,一樣也會共用一個事件循環。
若是 Future 執行體已經執行完畢了,但你又拿着這個 Future 的引用,往裏面加了一個 then 方法體,這時 Dart 會如何處理呢?面對這種狀況,Dart 會將後續加入的 then 方法體放入微任務隊列,儘快執行。
//f1比f2先執行
Future(() => print('f1'));
Future(() => print('f2'));
//f3執行後會馬上同步執行then 3
Future(() => print('f3')).then((_) => print('then 3'));
//then 4會加入微任務隊列,儘快執行
Future(() => null).then((_) => print('then 4'));
結果: f1 f2 f3 then 3 then 4複製代碼
Future 是異步任務的封裝,藉助於 await 與 async,咱們能夠經過事件循環實現非阻塞的同步等待。Dart 中的 await 並非阻塞等待,而是異步等待。Dart 會將調用體的函數也視做異步函數,將等待語句的上下文放入 Event Queue 中,一旦有告終果,Event Loop 就會把它從 Event Queue 中取出,等待代碼繼續執行。
將async
關鍵字做爲方法聲明的後綴時,具備以下意義
Future
對象做爲返回值// 導入io庫,調用sleep函數
import 'dart:io';
// 模擬耗時操做,調用sleep函數睡眠2秒
doTask() async{
await sleep(const Duration(seconds:2));
return "Ok";
}
// 定義一個函數用於包裝
test() async {
var r = await doTask();
print(r);
}
void main(){
print("main start");
test();
print("main end");
}
結果:
main start
main end
Ok複製代碼
咱們先來看下這段代碼。第二行的 then 執行體 f2 是一個 Future,爲了等它完成再進行下一步操做,咱們使用了 await,指望打印結果爲 f一、f二、f三、f4:
Future(()=>print('f1'))
.then((_)async=>awaitFuture(()=>print('f2')))
.then((_)=>print('f3'));
Future(()=>print('f4'));複製代碼
實際上,當你運行這段代碼時就會發現,打印出來的結果實際上是 f一、f四、f二、f3!
因爲 await 是採用事件隊列的機制實現等待行爲的,因此比它先在事件隊列中的 f4 並不會被它阻塞。
Dart 也提供了多線程機制,即 Isolate(這個單詞的中文意思是隔離)。在 Isolate 中,資源隔離作得很是好,每一個 Isolate 都有本身的 Event Loop 與 Queue,Isolate 之間不共享任何資源,只能依靠消息機制通訊,所以也就沒有資源搶佔問題。以下所示,咱們聲明瞭一個 Isolate 的入口函數,而後在 main 函數中啓動它,並傳入了一個字符串參數:
doSth(msg) => print(msg);
main() {
Isolate.spawn(doSth, "Hi");
...
}複製代碼
那麼如何利用消息機制進行通訊呢,下面引用了一篇文章的講解,圖畫的很好。
整個消息通訊過程如上圖所示,兩個Isolate是經過兩對Port對象通訊,一對Port分別由用於接收消息的ReceivePort
對象,和用於發送消息的SendPort
對象構成。其中SendPort
對象不用單首創建,它已經包含在ReceivePort
對象之中。須要注意,一對Port對象只能單向發消息,這就如同一根自來水管,ReceivePort
和SendPort
分別位於水管的兩頭,水流只能從SendPort
這頭流向ReceivePort
這頭。所以,兩個Isolate
之間的消息通訊確定是須要兩根這樣的水管的,這就須要兩對Port對象。