Flutter 95: 圖解 Dart 單線程實(shí)現(xiàn)異步處理之 Task Queue

??????小菜前段時(shí)間簡單研究了一下 Dart 單線程實(shí)現(xiàn)異步的操作,今天繼續(xù)學(xué)習(xí) Dart 的事件機(jī)制的任務(wù)調(diào)度乌妙;

任務(wù)調(diào)度

??????Dart 是單線程的,一個(gè) Flutter 程序由一個(gè)或多個(gè) isolate 組成建钥,默認(rèn)的執(zhí)行方法均是在 main isolate 中藤韵;一個(gè) isolate 中包含一個(gè) Event Loop 和一個(gè) Task Queue,而 Task Queue 包含 MicroTask Queue 微事件隊(duì)列和 Event Queue 事件隊(duì)列兩種熊经;


??????Dart 的事件機(jī)制是根據(jù)任務(wù)調(diào)度優(yōu)先級來實(shí)現(xiàn)的泽艘;其中將任務(wù)添加到 MicroTask Queue 微事件隊(duì)列的方式有 scheduleMicrotask()Future.microtask() 兩種;而將任務(wù)添加到 Event Queue 事件隊(duì)列一般通過 Future 的相關(guān)構(gòu)造方法實(shí)現(xiàn)奈搜;

??????MicroTask Queue 微事件隊(duì)列的執(zhí)行優(yōu)先級高于 Event Queue 事件隊(duì)列悉盆,而小菜簡單理解,兩種均類似于 Android 線程中的異步操作馋吗,只是 MicroTask Queue 微事件隊(duì)列優(yōu)先級相對更高焕盟;


Dart 的事件執(zhí)行順序如圖所示;

  1. 啟動(dòng) app 后優(yōu)先執(zhí)行 main() 方法中的同步方法宏粤;
  2. 查看 MicroTask Queue 是否為空脚翘,若不為空,優(yōu)先循環(huán)執(zhí)行 MicroTask Queue 中的 MicroTask绍哎,直到隊(duì)列為空来农;
  3. MicroTask Queue 隊(duì)列為空則查看 Event Queue 事件隊(duì)列,若不為空崇堰,則循環(huán)執(zhí)行 Event Queue 中的 Event 事件沃于,直到隊(duì)列為空;
  4. 等兩個(gè)隊(duì)列的任務(wù)均執(zhí)行完成后結(jié)束海诲;

??????Tips: 當(dāng)任務(wù)隊(duì)列執(zhí)行 MicroTask Queue 微事件隊(duì)列時(shí)繁莹,Event Queue 事件隊(duì)列被卡住,即應(yīng)用無法繪制圖形特幔,處理鼠標(biāo)點(diǎn)擊事件咨演,無法對 I/O 事件做出反應(yīng)等;

案例嘗試

??????每個(gè) isolate 有各自的內(nèi)存塊和 Event Loop 是互相隔離的蚯斯,小菜只嘗試單個(gè) isolate 中的微事件隊(duì)列和事件隊(duì)列的執(zhí)行順序薄风;

  1. 小菜優(yōu)先嘗試最基本的 Future 構(gòu)造函數(shù)和 MicroTask 的執(zhí)行順序饵较;
_taskQueue01() {
  Future(() => print('TaskQueue -> Future A'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask B'));
  Future(() => print('TaskQueue -> Future C'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
}

??????根據(jù)上述執(zhí)行順序,首先執(zhí)行 main() 中同步的日志輸出 Click start -> end遭赂,之后優(yōu)先執(zhí)行 MicroTask 微事件 scheduleMicrotask A -> C循诉;再之后執(zhí)行添加到 EventTask 事件隊(duì)列的 Future() 構(gòu)造函數(shù) Future A -> C;最后執(zhí)行因延遲 2s 加入事件隊(duì)列的 Future B嵌牺;

  1. 小菜在【案例一】的基礎(chǔ)上嘗試添加 Future.microtask() 構(gòu)造函數(shù)打洼;
_taskQueue02() {
  Future(() => print('TaskQueue -> Future A'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'));
  Future.microtask(() => print('TaskQueue -> Future.microtask B'));
  Future(() => print('TaskQueue -> Future C'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
}

??????簡單了解 Future.microtask() 函數(shù)源碼,其實(shí)就是封裝了一個(gè) scheduleMicrotask() 微事件逆粹,所以微事件隊(duì)列執(zhí)行順序?yàn)?scheduleMicrotask A -> B -> C募疮;

  1. 小菜在【案例二】的基礎(chǔ)上嘗試添加 Future.sync() 構(gòu)造函數(shù);
_taskQueue03() {
  Future(() => print('TaskQueue -> Future A'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.sync(() => print('TaskQueue -> Future.sync D'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'));
  Future.microtask(() => print('TaskQueue -> Future.microtask B'));
  Future(() => print('TaskQueue -> Future C'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
}

??????Future.sync() 為同步方法僻弹,會(huì)立即執(zhí)行阿浓,因此是在 main() 點(diǎn)擊同步日志 startend 之間的;

  1. 小菜嘗試添加 Future.then() 級聯(lián)方法蹋绽;
_taskQueue04() {
  Future(() => print('TaskQueue -> Future A'))
      .then((_) => print('TaskQueue -> Future A -> then()01'))
      .then((_) => print('TaskQueue -> Future A -> then()02'));
  Future.sync(() => print('TaskQueue -> Future.sync D'))
      .then((_) => print('TaskQueue -> Future.sync D -> then()01'))
      .then((_) => print('TaskQueue -> Future.sync D -> then()02'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()01'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()02'));
  Future.microtask(() => print('TaskQueue -> Future.microtask B'))
      .then((_) => print('TaskQueue -> Future.microtask B -> then()01'))
      .then((_) => print('TaskQueue -> Future.microtask B -> then()02'));
  Future(() => print('TaskQueue -> Future C'))
      .then((_) => print('TaskQueue -> Future C -> then()01'))
      .then((_) => print('TaskQueue -> Future C -> then()02'));
}

??????Future.then() 級聯(lián)函數(shù)是按照順序執(zhí)行的芭毙,執(zhí)行完第一個(gè) then() 函數(shù)后才會(huì)執(zhí)行第二個(gè) then();且小菜理解為 then() 函數(shù)會(huì)在 Future() 執(zhí)行之后立即執(zhí)行卸耘,可以看作是存放在微事件隊(duì)列中退敦;因此整體的執(zhí)行順序是 sync()-> sync().then() -> microtask() -> microtask().then() -> Future() -> Future().then() -> delayed() -> delayed().then()

  1. 小菜在【案例三 + 四】的基礎(chǔ)上添加 Future.whenComplete() 方法蚣抗;
_taskQueue05() {
  Future(() => print('TaskQueue -> Future A'))
      .then((_) => print('TaskQueue -> Future A -> then()01'))
      .then((_) => print('TaskQueue -> Future A -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future A -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.sync(() => print('TaskQueue -> Future.sync D'))
      .then((_) => print('TaskQueue -> Future.sync D -> then()01'))
      .then((_) => print('TaskQueue -> Future.sync D -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future.sync D -> whenComplete()'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()01'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future.delayed B -> whenComplete()'));
  Future.microtask(() => print('TaskQueue -> Future.microtask B'))
      .then((_) => print('TaskQueue -> Future.microtask B -> then()01'))
      .then((_) => print('TaskQueue -> Future.microtask B -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future.microtask B -> whenComplete()'));
  Future(() => print('TaskQueue -> Future C'))
      .then((_) => print('TaskQueue -> Future C -> then()01'))
      .then((_) => print('TaskQueue -> Future C -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future C -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
}

??????Future.whenComplete() 是在 Future()then() 函數(shù)執(zhí)行完成之后立即執(zhí)行侈百;小菜也簡單看作是存放在微事件隊(duì)列中;依照小菜的測試案例來看翰铡,sync 執(zhí)行同步任務(wù)完成后钝域,會(huì)循環(huán)遍歷微事件隊(duì)列,其中 scheduleMicrotask A 是在 sync() 函數(shù)之前加入 MicroTask Queue 中锭魔,因此是先執(zhí)行 scheduleMicrotask A 再執(zhí)行 sync() 之后的 then()whenComplete() 方法例证;

  1. 小菜嘗試在 then() 函數(shù)中執(zhí)行新的事件隊(duì)列;
_taskQueue06() {
  Future(() => print('TaskQueue -> Future A'))
      .then((_) {
        print('TaskQueue -> Future A -> then()01');
        return Future.delayed(Duration(seconds: 1), () => print('TaskQueue -> Future.delayed D'));
      })
      .then((_) => print('TaskQueue -> Future A -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future A -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()01'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future.delayed B -> whenComplete()'));
  Future(() => print('TaskQueue -> Future C'))
      .then((_) {
        print('TaskQueue -> Future C -> then()01');
        return scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
      })
      .then((_) => print('TaskQueue -> Future C -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future C -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask B'));
}

????a. 首先執(zhí)行 MicroTask Queue 微事件隊(duì)列中的 scheduleMicrotask A -> B迷捧;
????b. 之后以 FIFO 先進(jìn)先出的順序執(zhí)行 EventTask Queue 事件隊(duì)列中 Task织咧;先執(zhí)行 Future A,之后是 Athen()01漠秋,此時(shí)小菜設(shè)置了一個(gè) 1s 的延遲 Future.delayed D烦感,因?yàn)?then() 的級聯(lián)函數(shù)是需要等前面的 then() 函數(shù)執(zhí)行完成后才能執(zhí)行;因此優(yōu)先執(zhí)行 EventTask Queue 中的 Future C膛堤;
????c. 執(zhí)行完 Future C 之后執(zhí)行 then()01,此時(shí)小菜設(shè)置了一個(gè) MicroTask晌该,依照小菜的理解肥荔,then() / whenComplete() 均存放在 MicroTask Queue 中绿渣,因此新設(shè)置的 MicroTask 會(huì)放置在 MicroTask Queue 末尾,等 then()02whenComplete() 再執(zhí)行
????d. 此時(shí) EventTask Queue 事件隊(duì)列中已執(zhí)行完畢燕耿,在 1s 后添加了新的 Future.delayed D 并執(zhí)行中符;
????e. 待 Future.delayed D 執(zhí)行結(jié)束或,Future A 的第二個(gè)級聯(lián) then() 函數(shù)和 whenComplete() 開始執(zhí)行誉帅;
????f. 最后執(zhí)行延遲最久的 2sFuture.delayed B 及其 then() / whenComplete() 函數(shù)淀散;

  1. 小菜在測試過程中,與【案例六】代碼幾乎一致蚜锨,只是在 Future.then() 中調(diào)用 Future() 時(shí)少了 return档插;
_taskQueue07() {
  Future(() => print('TaskQueue -> Future A'))
      .then((_) {
        print('TaskQueue -> Future A -> then()01');
        Future.delayed(Duration(seconds: 1), () => print('TaskQueue -> Future.delayed D'));
      })
      .then((_) => print('TaskQueue -> Future A -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future A -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()01'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future.delayed B -> whenComplete()'));
  Future(() => print('TaskQueue -> Future C'))
      .then((_) {
        print('TaskQueue -> Future C -> then()01');
        scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
      })
      .then((_) => print('TaskQueue -> Future C -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future C -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask B'));
}

??????小菜測試發(fā)現(xiàn),雖然只是少了兩個(gè) return 但是執(zhí)行順序卻變化很大亚再;
????a. 首先執(zhí)行微事件隊(duì)列中的 scheduleMicrotask A -> B
????b. 之后執(zhí)行 EventTask Queue 中的 Future A郭膛,執(zhí)行第一個(gè) print(then()01) 之后,小菜設(shè)置了 Future.delayed D氛悬,因?yàn)闊o需返回则剃,所以將 Future.delayed D 事件隊(duì)列末位后第一個(gè) then() 函數(shù)已完成執(zhí)行,此時(shí)可以執(zhí)行第二個(gè) then()02whenComplete() 函數(shù)如捅;
????c. 繼續(xù)執(zhí)行 EventTask Queue 中的 Future C棍现,執(zhí)行第一個(gè) print(then()01) 之后,小菜設(shè)置了 scheduleMicrotask() C镜遣,無需 return己肮,將 scheduleMicrotask() C 添加到微事件隊(duì)列末位,此時(shí)第一個(gè) then() 函數(shù)以完成執(zhí)行烈涮,執(zhí)行微事件隊(duì)列中的第二個(gè) then()02whenComplete() 函數(shù)朴肺,再之后執(zhí)行微事件隊(duì)列中最后的 scheduleMicrotask() C
????d. 之后執(zhí)行事件隊(duì)列末位的 1sFuture.delayed D坚洽,此時(shí)事件隊(duì)列已為空戈稿;
????e. 最后執(zhí)行 2s 添加到事件隊(duì)列中的 Future.delayed B 及其 then() / whenComplete() 函數(shù);

匯總小結(jié)

  1. Dart 的任務(wù)調(diào)度模型是單線程輪詢讶舰,不是基于時(shí)間調(diào)度的鞍盗;我們可以知道各個(gè)事件的調(diào)度順序,但無法明確得知各個(gè)事件調(diào)度的時(shí)間跳昼;例如:延遲 2s 并非一定是 2s 后執(zhí)行般甲,需要等之前的任務(wù)調(diào)度結(jié)束后才會(huì)執(zhí)行;
  2. 注意級聯(lián)函數(shù)中的新的任務(wù)調(diào)度是否需要返回結(jié)果后再繼續(xù)鹅颊;
  3. 實(shí)際中盡量在 EventTask 中執(zhí)行耗時(shí)操作敷存,減少在 MicroTask 中執(zhí)行;若任務(wù)耗時(shí)時(shí)間較長可以嘗試用 isolate 開啟新的異步線程執(zhí)行;

??????Task Queue 案例嘗試


??????小菜對任務(wù)調(diào)度的理解還不夠深入锚烦,如有錯(cuò)誤請多多指導(dǎo)觅闽!

來源: 阿策小和尚

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市涮俄,隨后出現(xiàn)的幾起案子蛉拙,更是在濱河造成了極大的恐慌,老刑警劉巖彻亲,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孕锄,死亡現(xiàn)場離奇詭異,居然都是意外死亡苞尝,警方通過查閱死者的電腦和手機(jī)畸肆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來野来,“玉大人恼除,你說我怎么就攤上這事÷眨” “怎么了豁辉?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舀患。 經(jīng)常有香客問我徽级,道長,這世上最難降的妖魔是什么聊浅? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任桶蛔,我火速辦了婚禮衷模,結(jié)果婚禮上挂疆,老公的妹妹穿的比我還像新娘避消。我一直安慰自己,他們只是感情好顽冶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布欺抗。 她就那樣靜靜地躺著,像睡著了一般强重。 火紅的嫁衣襯著肌膚如雪绞呈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天间景,我揣著相機(jī)與錄音佃声,去河邊找鬼。 笑死倘要,一個(gè)胖子當(dāng)著我的面吹牛圾亏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼召嘶,長吁一口氣:“原來是場噩夢啊……” “哼父晶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起弄跌,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尝苇,沒想到半個(gè)月后铛只,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糠溜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年淳玩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片非竿。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜕着,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出红柱,到底是詐尸還是另有隱情承匣,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布锤悄,位于F島的核電站韧骗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏零聚。R本人自食惡果不足惜袍暴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望隶症。 院中可真熱鬧政模,春花似錦、人聲如沸蚂会。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颂龙。三九已至习蓬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間措嵌,已是汗流浹背躲叼。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留企巢,地道東北人枫慷。 一個(gè)月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親或听。 傳聞我的和親對象是個(gè)殘疾皇子探孝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354