深入理解Flutter多線程

該文章屬于劉小壯原創(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 queuemicrotask queue事件隊列鲫忍,eventmicrotask隊列有點類似iOS的source0source1

  • 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姊途、awaitFuture知态,其中asyncawait需要一起使用捷兰。在Dart中可以通過asyncawait進行異步操作,async表示開啟一個異步操作负敏,也可以返回一個Future結(jié)果贡茅。如果沒有返回值,則默認(rèn)返回一個返回值為nullFuture

async顶考、await本質(zhì)上就是Dart對異步操作的一個語法糖赁还,可以減少異步調(diào)用的嵌套調(diào)用,并且由async修飾后返回一個Future驹沿,外界可以以鏈?zhǔn)秸{(diào)用的方式調(diào)用艘策。這個語法是JSES7標(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),例如延時操作却妨、異步操作等饵逐。下面是一個很簡單的延時操作,通過Futuredelayed方法實現(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é)程的特性呢?后來想了一下塞耕,awaitdispatch_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

isolateDart平臺對線程的實現(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的消息機制唾糯,通過建立通信雙方的sendPortreceiveport怠硼,進行相互的消息傳遞鬼贱,在Dart中叫做消息傳遞。

從上面例子中可以看出香璃,在進行isolate消息傳遞的過程中这难,本質(zhì)上就是進行port的傳遞。將port傳遞給其他isolate葡秒,其他isolate通過port拿到sendPort姻乓,向調(diào)用方發(fā)送消息來進行相互的消息傳遞。

Embedder

正如其名眯牧,Embedder是一個嵌入層蹋岩,將Flutter嵌入到各個平臺上。Embedder負責(zé)范圍包括原生平臺插件学少、線程管理剪个、事件循環(huán)等。

Flutter System Overriew

Embedder中存在四個Runner旱易,四個Runner分別如下禁偎。其中每個Flutter Engine各自對應(yīng)一個UI RunnerGPU Runner阀坏、IO Runner如暖,但所有Engine共享一個Platform Runner

Embedder

Runnerisolate并不是一碼事忌堂,彼此相互獨立盒至。以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 IsolateEngine提交一次渲染幀時媒役,Engine會等待下次vsync祝谚,當(dāng)下次vsync到來時,由Root IsolateWidgets進行布局操作酣衷,并生成頁面的顯示信息的描述交惯,并將信息交給Engine去處理。

由于對widgets進行layout并生成layer treeUI Runner進行的穿仪,如果在UI Runner中進行大量耗時處理席爽,會影響頁面的顯示,所以應(yīng)該將耗時操作交給其他isolate處理啊片,例如來自Native Plugin的事件只锻。

Rendering Pipeline.jpg

GPU Runner

GPU Runner并不直接負責(zé)渲染操作,其負責(zé)GPU相關(guān)的管理和調(diào)度紫谷。當(dāng)layer tree信息到來時齐饮,GPU Runner將其提交給指定的渲染平臺,渲染平臺是Skia配置的碴里,不同平臺可能有不同的實現(xiàn)沈矿。

GPU Runner相對比較獨立,除了Embedder外其他線程均不可向其提交渲染信息咬腋。

Graphics Pipeline

IO Runner

一些GPU Runner中比較耗時的操作羹膳,就放在IO Runner中進行處理,例如圖片讀取根竿、解壓陵像、渲染等操作就珠。但是只有GPU Runner才能對GPU提交渲染信息,為了保證IO Runner也具備這個能力醒颖,所以IO Runner會引用GPU Runnercontext妻怎,這樣就具備向GPU提交渲染信息的能力。


簡書由于排版的問題泞歉,閱讀體驗并不好逼侦,布局、圖片顯示腰耙、代碼等很多問題榛丢。所以建議到我Github上,下載Flutter編程指南 PDF合集挺庞。把所有Flutter文章總計三篇晰赞,都寫在這個PDF中,而且左側(cè)有目錄选侨,方便閱讀掖鱼。

Flutter編程指南

下載地址:Flutter編程指南 PDF
麻煩各位大佬點個贊,謝謝援制!??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戏挡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子隘谣,更是在濱河造成了極大的恐慌增拥,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寻歧,死亡現(xiàn)場離奇詭異掌栅,居然都是意外死亡,警方通過查閱死者的電腦和手機码泛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門猾封,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人噪珊,你說我怎么就攤上這事晌缘。” “怎么了痢站?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵磷箕,是天一觀的道長。 經(jīng)常有香客問我阵难,道長岳枷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮空繁,結(jié)果婚禮上殿衰,老公的妹妹穿的比我還像新娘。我一直安慰自己盛泡,他們只是感情好闷祥,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著傲诵,像睡著了一般凯砍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拴竹,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天果覆,我揣著相機與錄音,去河邊找鬼殖熟。 笑死,一個胖子當(dāng)著我的面吹牛斑响,可吹牛的內(nèi)容都是我干的菱属。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼舰罚,長吁一口氣:“原來是場噩夢啊……” “哼纽门!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起营罢,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤赏陵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饲漾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蝙搔,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年考传,在試婚紗的時候發(fā)現(xiàn)自己被綠了吃型。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡僚楞,死狀恐怖勤晚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情泉褐,我是刑警寧澤赐写,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站膜赃,受9級特大地震影響挺邀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一悠夯、第九天 我趴在偏房一處隱蔽的房頂上張望癌淮。 院中可真熱鬧,春花似錦沦补、人聲如沸乳蓄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虚倒。三九已至,卻和暖如春产舞,著一層夾襖步出監(jiān)牢的瞬間魂奥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工易猫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耻煤,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓准颓,卻偏偏與公主長得像哈蝇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子攘已,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344