Flutter單線程模型之Isolate

1. 什么是Ioslate

我們的flutter應(yīng)用啟動(dòng)的時(shí)候就會(huì)開辟一個(gè)獨(dú)立的ioslate,這里面包含了一個(gè)獨(dú)立的內(nèi)存空間和一個(gè)攜帶 event loops的單一線程垛吗,這個(gè)單一線程只處理事件循環(huán)咏窿,我們所有dart代碼都在ioslate里面執(zhí)行息拜,所有的事件廉丽,例如布局構(gòu)建和拆除缠黍,異步任務(wù),io事件等都是在這里面執(zhí)行药蜻,每個(gè)事件都會(huì)被加入到一個(gè)事件隊(duì)列中瓷式,由event loops從隊(duì)列中按照先進(jìn)先出的方式取出事件依次執(zhí)行替饿。(和android的handler機(jī)制挺像的嘛)

image.png

如上圖所示,就代表一個(gè)獨(dú)立的ioslate 它就像我們機(jī)器上面開辟的一個(gè)小空間贸典,綠色部分就代表一個(gè)獨(dú)立的內(nèi)存空間视卢,紅色部就代表事件循環(huán),在這種方式下廊驼,我們是不能直接在dart中做大數(shù)據(jù)量的計(jì)算的据过,寫flutter的開發(fā)者基本都是從android或ios轉(zhuǎn)過來的,在android那邊妒挎,對(duì)于大數(shù)據(jù)量的計(jì)算绳锅,為了不阻塞ui線程,我們可以使用開啟異步任務(wù)執(zhí)行大數(shù)據(jù)量的計(jì)算酝掩,但是在dart這邊是不能這么做的鳞芙,因?yàn)閐art默認(rèn)所有代碼都在默認(rèn)的ioslate中執(zhí)行,而異步任務(wù)async對(duì)于ioslate來說也只是一個(gè)事件而已期虾,而事件循環(huán)只有在線程空閑的時(shí)候才會(huì)從事件隊(duì)列中取出事件執(zhí)行原朝,大數(shù)據(jù)量的計(jì)算導(dǎo)致線程不空閑,從而阻塞事件隊(duì)列镶苞,導(dǎo)致應(yīng)用掉幀喳坠,對(duì)于這個(gè)問題,我們可以使用Isolate.spawn()Flutter's compute()函數(shù)新建獨(dú)立的ioslate執(zhí)行大數(shù)據(jù)量的計(jì)算茂蚓,讓我們的 main isolate可以有空閑的時(shí)間來處理小部件的重建和銷毀壕鹉。

image.png

這些新建的isolate雖然由main isolate創(chuàng)建,但是main ioslate卻不能直接訪問child ioslate的內(nèi)存煌贴,這就是ioslate的名字由來:這些小空間彼此隔離御板。

不同ioslate之間可以使用ReceivePort相互訪問,他們之間唯一的工作方式就是通過不停的消息傳遞將事件傳遞給對(duì)方牛郑,在將事件加入到自己的事件隊(duì)列中怠肋。

2. event loops

image.png

如圖所示,如果把應(yīng)用的生命周期比作一條時(shí)間線淹朋,那么在這條時(shí)間線中會(huì)發(fā)生各種事件笙各,例如點(diǎn)擊事件,widget的構(gòu)建和銷毀础芍,io事件等杈抢,而這些事件會(huì)在任何時(shí)刻以任意的組合方式出現(xiàn)在事件隊(duì)列中,event loops 會(huì)在空閑的時(shí)候從事件隊(duì)列中按照先進(jìn)先出的方式取出事件執(zhí)行仑性,一直到事件隊(duì)列被清空為止惶楼,當(dāng)事件隊(duì)列被清空,并且所有的事件都被執(zhí)行結(jié)束后,ioslate中的線程就會(huì)掛起歼捐,這個(gè)時(shí)候就可以執(zhí)行g(shù)c操作何陆,清除內(nèi)存。

而異步編程就是建立在這樣的基礎(chǔ)上的豹储,如下代碼所示:

RaisedButton( //1. a event to build RaisedButton
  child: Text('Click me'),
  onPressed: () { //2. a event to send onTap 
    final myFuture = http.get('https://example.com'); //3. a event to send request 
    myFuture.then((response) { 
      if (response.statusCode == 200) {
        print('Success!');
      }
    });
  },
)

在代碼中贷盲,發(fā)起了三個(gè)事件:

  1. 構(gòu)建一個(gè)RaisedButton
  2. 用戶發(fā)起的tap事件
  3. 發(fā)起一個(gè)異步任務(wù),請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)

不管是構(gòu)建部件剥扣,還是異步任務(wù)巩剖,甚至異步任務(wù)的回調(diào),對(duì)于 event loops來說钠怯,他們都只是一個(gè)事件而已佳魔,所以event loops它不管接下來出現(xiàn)的是什么事件,它只管從事件隊(duì)列中取出事件執(zhí)行呻疹,只要線程處于空閑下吃引,它就不停的取出隊(duì)列中的任務(wù)執(zhí)行。上面的代碼會(huì)向 event loops中塞入一個(gè)構(gòu)建按鈕的事件刽锤,執(zhí)行完后等待用戶點(diǎn)擊事件的到來镊尺,當(dāng)用戶點(diǎn)擊事件到來后會(huì)告訴flutter點(diǎn)擊的坐標(biāo),flutter會(huì)根據(jù)坐標(biāo)從渲染系統(tǒng)中找出對(duì)應(yīng)的按鈕的onPressed屬性并思,執(zhí)行相應(yīng)的代碼庐氮,這個(gè)時(shí)候,又會(huì)發(fā)出一個(gè)網(wǎng)絡(luò)請(qǐng)求宋彼,并且注冊(cè)回調(diào)弄砍,但是這對(duì)于 event loops來說也就是一個(gè)事件而已, event loops只管執(zhí)行事件输涕,當(dāng)網(wǎng)絡(luò)請(qǐng)求結(jié)束后會(huì)發(fā)起回調(diào)音婶。

3. event loops處理的事件

  • I/O
  • gesture
  • drawing
  • timers
  • streams
  • futures

4. isolate(隔離區(qū))的使用和通信

1. 創(chuàng)建隔離區(qū)

可以使用Isolate.spawn()Flutter's compute()函數(shù)新建新的隔離區(qū),這個(gè)過程中莱坎,會(huì)新建新的線程和event loops衣式,分配新的cpu資源和內(nèi)存區(qū)域,代碼如下

import 'dart:isolate';

void main() {
  Isolate.spawn(isolate, "true");
}

void isolate(String data) {
  print("isolate ${data}");
}

2. 主隔離區(qū)

flutter應(yīng)用會(huì)創(chuàng)建一個(gè)默認(rèn)的隔離區(qū)檐什,可以使用下面的代碼訪問

Isolate.current

3. 隔離區(qū)之間的通信

使用ReceivePort實(shí)現(xiàn)不同隔離區(qū)的通信

import 'dart:isolate';

late Isolate isolate;

void sendMsg(SendPort sendPort) {
  sendPort.send('hellow wrold');
}

main() async {
  final receivePort = ReceivePort();
  isolate = await Isolate.spawn(sendMsg, receivePort.sendPort);
  receivePort.listen((message) {
    print(message); 
  });
}

4. 銷毀隔離區(qū)

銷毀隔離區(qū)的代碼如下

isolate.kill();

5. MicroTask queue

image.png

如上圖所示碴卧,我們的flutter應(yīng)用在創(chuàng)建后不僅有 event quene,還存在著 microTask queue(微任務(wù)隊(duì)列)乃正,而且在事件循環(huán)中住册,microTask queue事件處理的優(yōu)先級(jí)其實(shí)是比event quene事件更高的,也就是說microTask queue中的事件會(huì)比event quene的事件更快被處理瓮具,event quene的事件必須等到microTask queue的事件被清空了才能被處理荧飞,所以說凡人,一般情況下,我們是不會(huì)用到MicroTask queue的垢箕,但是一旦用到了划栓,我們不能在microTask queue中做特別耗時(shí)的任務(wù),否則會(huì)導(dǎo)致event quene的事件無法被處理条获,而widget的構(gòu)建和刷新是event quene的事件,這樣就會(huì)導(dǎo)致UI卡頓掉幀

6.最終思考與總結(jié)

1. 思考

用Future做網(wǎng)絡(luò)請(qǐng)求不會(huì)導(dǎo)致卡頓蒋歌,這是為什么

既然Future對(duì)于event loops來說只是一個(gè)事件帅掘,使用Future做耗時(shí)任務(wù)會(huì)導(dǎo)致UI卡頓,那為什么我們使用Future做網(wǎng)絡(luò)請(qǐng)求(時(shí)長(zhǎng)可能有十幾秒)的時(shí)候不會(huì)導(dǎo)致掉幀或者卡頓呢堂油?這是因?yàn)榫W(wǎng)絡(luò)請(qǐng)求(http.get())就不是在dart層完成的修档,它其實(shí)是由dart層告訴操作系統(tǒng),操作系統(tǒng)開啟了一個(gè)異步任務(wù)完成的府框,操作系統(tǒng)做完網(wǎng)絡(luò)請(qǐng)求后將數(shù)據(jù)再告訴dart層吱窝,dart層再完成一個(gè)簡(jiǎn)單的讀操作,這就是為什么一個(gè)網(wǎng)絡(luò)請(qǐng)求可以長(zhǎng)達(dá)十幾秒又不卡UI的原因迫靖,因?yàn)榫W(wǎng)絡(luò)請(qǐng)求就不是dart層完成的

知識(shí)儲(chǔ)備(external關(guān)鍵字)

external是flutter提供的一個(gè)關(guān)鍵字院峡,可以在非抽象類中實(shí)現(xiàn)類似抽象方法的方法,將方法的聲明和實(shí)現(xiàn)相分離系宜,
external的作用如下:

  1. 可以在非抽象類中實(shí)現(xiàn)類似抽象方法的方法照激,將方法的聲明和實(shí)現(xiàn)相分離,這樣可以實(shí)現(xiàn)在不同的平臺(tái)盹牧,不管是dart for vm, 還是dart for web, 上層應(yīng)用都可以只使用一套api俩垃,然后由不同的平臺(tái)各種對(duì)external 修飾的方法添加實(shí)現(xiàn),有助于提升擴(kuò)展性汰寓,減低應(yīng)用實(shí)現(xiàn)跨平臺(tái)的難度

  2. external 聲明的方法由底層sdk根據(jù)不同的平臺(tái)(vm或者web)添加實(shí)現(xiàn)口柳,方法所在類不用聲明為抽象類,所以可以直接實(shí)例化

源碼解析

我們查看Dio源碼有滑,發(fā)現(xiàn)使用Dio做網(wǎng)絡(luò)請(qǐng)求跃闹,其實(shí)網(wǎng)絡(luò)請(qǐng)求會(huì)交給HttpClient,由HttpClient的openUrl方法開啟網(wǎng)絡(luò)請(qǐng)求俺孙,而openUrl其實(shí)會(huì)調(diào)用到一個(gè)external方法辣卒,代碼如下

image.png

我們?cè)趂lutter3.3.2\flutter\bin\cache\dart-sdk\lib_internal\vm\socket_patch.dart找到了對(duì)應(yīng)的實(shí)現(xiàn),代碼如下

image.png

我們繼續(xù)查看這個(gè)_startConnect方法的調(diào)用過程睛榄,發(fā)現(xiàn)最終會(huì)調(diào)用到另一個(gè)external方法荣茫,代碼如下

image.png

nativeCreateUnixDomainConnect也是一個(gè)external方法,我們暫時(shí)沒有找到它的實(shí)現(xiàn)场靴,但是我們其實(shí)可以知道Unix domain socket是一種終端啡莉,可以使同一臺(tái)操作系統(tǒng)上的兩個(gè)或多個(gè)進(jìn)程進(jìn)行數(shù)據(jù)通信港准,然后我們?cè)倏纯磏ativeCreateUnixDomainConnect方法所在類的一些注釋,會(huì)發(fā)現(xiàn)一些關(guān)鍵信息咧欣,代碼如下

image.png

畫了紅圈是關(guān)鍵的一句注釋浅缸,這句注釋的意思就是說_NativeSocket封裝了一個(gè)操作系統(tǒng)的socket,os是操作系統(tǒng)的意思魄咕,也就是說調(diào)用socket.nativeCreateUnixDomainConnect方法的時(shí)候會(huì)到調(diào)用操作系統(tǒng)的socket衩椒,也就是說網(wǎng)絡(luò)請(qǐng)求其實(shí)是操作系統(tǒng)完成的,這就是為什么flutter應(yīng)用是單線程模型的應(yīng)用哮兰,但是在默認(rèn)的isolate做網(wǎng)絡(luò)請(qǐng)求卻不會(huì)卡 UI 的原因毛萌,因?yàn)榫W(wǎng)絡(luò)請(qǐng)求就不是dart層完成的,是操作系統(tǒng)完成的

2. 總結(jié)

我們了解了dart的單線程模型和事件機(jī)制喝滞,知道了widget的構(gòu)建和刷新是在事件循環(huán)的條件下完成的阁将,這使我們知道UI掉幀的本質(zhì)原因(event queue 的UI刷新事件沒有及時(shí)被處理),也知道了新建future和microTask 無法解決UI卡頓的問題右遭,網(wǎng)絡(luò)請(qǐng)求不會(huì)卡UI是因?yàn)榫W(wǎng)絡(luò)請(qǐng)求就不是dart層完成的做盅,是操作系統(tǒng)完成的,還知道了如何新建隔離區(qū)窘哈,這可以為我們?nèi)蘸笞鯱I流暢度優(yōu)化提供更多思路吹榴,帶領(lǐng)我們走向正確的優(yōu)化方向

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宵距,隨后出現(xiàn)的幾起案子腊尚,更是在濱河造成了極大的恐慌,老刑警劉巖满哪,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婿斥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡哨鸭,警方通過查閱死者的電腦和手機(jī)民宿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來像鸡,“玉大人活鹰,你說我怎么就攤上這事≈还溃” “怎么了志群?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蛔钙。 經(jīng)常有香客問我锌云,道長(zhǎng),這世上最難降的妖魔是什么吁脱? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任桑涎,我火速辦了婚禮彬向,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘攻冷。我一直安慰自己娃胆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布等曼。 她就那樣靜靜地躺著里烦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪禁谦。 梳的紋絲不亂的頭發(fā)上招驴,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音枷畏,去河邊找鬼。 笑死虱饿,一個(gè)胖子當(dāng)著我的面吹牛拥诡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播氮发,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼渴肉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了爽冕?” 一聲冷哼從身側(cè)響起仇祭,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颈畸,沒想到半個(gè)月后乌奇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眯娱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年礁苗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徙缴。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡试伙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出于样,到底是詐尸還是另有隱情疏叨,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布穿剖,位于F島的核電站蚤蔓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏携御。R本人自食惡果不足惜昌粤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一既绕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涮坐,春花似錦凄贩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捷雕,卻和暖如春椒丧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背救巷。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工壶熏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人浦译。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓棒假,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親精盅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帽哑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Flutter默認(rèn)是單線程任務(wù)處理的,如果不開啟新的線程叹俏,任務(wù)默認(rèn)在主線程中處理妻枕。 事件隊(duì)列 和iOS應(yīng)用很像,在...
    羈擁_f357閱讀 577評(píng)論 0 1
  • 小菜前段時(shí)間簡(jiǎn)單研究了一下 Dart 單線程實(shí)現(xiàn)異步的操作粘驰,今天繼續(xù)學(xué)習(xí) Dart 的事件機(jī)制的任務(wù)調(diào)度屡谐; 任務(wù)調(diào)...
    阿策神奇閱讀 1,434評(píng)論 0 5
  • 事件循環(huán)在UI框架里面應(yīng)該算是一個(gè)常見的東西,例如安卓主線程里面就有個(gè)Looper一直在MessageQueue里...
    嘉偉咯閱讀 1,411評(píng)論 0 3
  • Dart 單線程模型 眾所周知,在Java中使用多線程來處理并發(fā)任務(wù)籽前,適量并合適地使用多線程亭珍,能夠極大地提高資源的...
    nero_i閱讀 874評(píng)論 0 1
  • 關(guān)于多線程與異步: 很多人容易把多線程和異步搞混,實(shí)際上這是兩個(gè)概念枝哄。多線程是開辟另外一個(gè)線程來處理事件肄梨,每個(gè)線程...
    冰棍兒好燙嘴閱讀 2,872評(píng)論 0 3