Flutter系列筆記-3.Dart之消息循環(huán)機(jī)制[轉(zhuǎn)載]

Dart之消息循環(huán)機(jī)制[轉(zhuǎn)載]

本文轉(zhuǎn)載自原文請(qǐng)查看 https://www.cnblogs.com/lxlx1798/p/11047163.html

概述

??異步任務(wù)在Dart中隨處可見,例如許多庫(kù)的方法調(diào)用都會(huì)返回Future對(duì)象來實(shí)現(xiàn)異步處理,我們也可以注冊(cè)Handler來響應(yīng)一些事件,如:鼠標(biāo)點(diǎn)擊事件,I/O流結(jié)束和定時(shí)器到期。

??這篇文章主要介紹了Dart中與異步任務(wù)相關(guān)的消息循環(huán)機(jī)制,閱讀完這篇文章后相信你可寫出更贊的異步執(zhí)行代碼。你也能學(xué)習(xí)到如何調(diào)度Future任務(wù)并且預(yù)測(cè)他們的執(zhí)行順序。

??在閱讀這篇文章之前,你最好先要了解一下基本的Future用法。

基本概念

??如果你寫過一些關(guān)于UI的代碼,你就應(yīng)該熟悉消息循環(huán)和消息隊(duì)列。有了他們才能保重UI的繪制操作和一些UI事件,如鼠標(biāo)點(diǎn)擊事件可以被一個(gè)一個(gè)的執(zhí)行從而保證UI和UI事件的統(tǒng)一性。

消息循環(huán)和消息隊(duì)列

??一個(gè)消息循環(huán)的職責(zé)就是不斷從消息隊(duì)列中取出消息并處理他們直到消息隊(duì)列為空。

????
eventqueue1.png

??消息隊(duì)列中的消息可能來自用戶輸入,文件I/O消息,定時(shí)器等。例如下圖的消息隊(duì)列就包含了定時(shí)器消息和用戶輸入消息。

?????
eventqueue2.png

??上述的這些概念你可能已經(jīng)駕輕就熟了,那接下來我們就討論一下這些概念在Dart中是怎么表現(xiàn)的?

Dart的單線程執(zhí)行

??當(dāng)一個(gè)Dart的方法開始執(zhí)行時(shí),他會(huì)一直執(zhí)行直至達(dá)到這個(gè)方法的退出點(diǎn)。換句話說Dart的方法是不會(huì)被其他Dart代碼打斷的。

Note:

??一個(gè)Dart的命令行應(yīng)用可以通過創(chuàng)建isolates來達(dá)到并行運(yùn)行的目的。

??isolates之間不會(huì)共享內(nèi)存,它們就像幾個(gè)運(yùn)行在不同進(jìn)程中的app,中能通過傳遞message來進(jìn)行交流。

??除了明確指出運(yùn)行在額外的isolates或者workers中的代碼外,所有的應(yīng)用代碼都是運(yùn)行在應(yīng)用的main isolate中。

??要了解更多相關(guān)內(nèi)容,可以查看https://www.dartlang.org/articles/event-loop/#use-isolates-or-workers-if-necessary

??正如下圖所示,當(dāng)一個(gè)Dart應(yīng)用開始的標(biāo)志是它的main isolate執(zhí)行了main方法。當(dāng)main方法退出后,main isolate的線程就會(huì)去逐一處理消息隊(duì)列中的消息

?????
darteventhandle.png

事實(shí)上,上圖是經(jīng)過簡(jiǎn)化的流程。

Dart的消息循環(huán)和消息隊(duì)列

  1. 一個(gè)Dart應(yīng)用有一個(gè)消息循環(huán)和兩個(gè)消息隊(duì)列event隊(duì)列和microtask隊(duì)列。

  2. event隊(duì)列包含所有外來的事件:I/O,mouse events,drawing events,timers,isolate之間的message等。

  3. microtask 隊(duì)列在Dart中是必要的,因?yàn)橛袝r(shí)候事件處理想要在稍后完成一些任務(wù)但又希望是在執(zhí)行下一個(gè)事件消息之前。

??event隊(duì)列包含Dart和來自系統(tǒng)其它位置的事件。但microtask隊(duì)列只包含來自當(dāng)前isolate的內(nèi)部代碼。

??正如下面的流程圖,當(dāng)main方法退出后,event循環(huán)就開始它的工作。首先它會(huì)以FIFO的順序執(zhí)行micro task,當(dāng)所有micro task執(zhí)行完后它會(huì)從event 隊(duì)列中取事件并執(zhí)行。如此反復(fù),直到兩個(gè)隊(duì)列都為空。

??????
dart-single-thread-pic.png

注意:當(dāng)事件循環(huán)正在處理micro task的時(shí)候。event隊(duì)列會(huì)被堵塞。這時(shí)候app就無法進(jìn)行UI繪制,響應(yīng)鼠標(biāo)事件和I/O等事件

??雖然你可以預(yù)測(cè)任務(wù)執(zhí)行的順序,但你無法準(zhǔn)確的預(yù)測(cè)到事件循環(huán)何時(shí)會(huì)處理你期望的任務(wù)。例如當(dāng)你創(chuàng)建一個(gè)延時(shí)1s的任務(wù),但在排在你之前的任務(wù)結(jié)束前事件循環(huán)是不會(huì)處理這個(gè)延時(shí)任務(wù)的,也就是或任務(wù)執(zhí)行可能是大于1s的。

通過鏈接的方式指定任務(wù)順序

??如果你的代碼之間存在依賴,那么盡量讓他們之間的依賴關(guān)系明確一點(diǎn)。明確的依賴關(guān)系可以很好的幫助其他開發(fā)者理解你的代碼,并且可以讓你的代碼更穩(wěn)定也更容易重構(gòu)。

先來看看下面這段

錯(cuò)誤的寫法:

// 這樣寫錯(cuò)誤的原因就是沒有明確體現(xiàn)出設(shè)置變量和使用變量之間的依賴關(guān)系
future.then(...set an important variable...);
Timer.run(() {...use the important variable...});

正確的寫法:

// 明確表現(xiàn)出了后者依賴前者設(shè)置的變量值
future.then(...set an important variable...)
  .then((_) {...use the important variable...});

??為了表示明確的前后依賴關(guān)系,我們可以使用then()()來表明要使用變量就必須要等設(shè)置完這個(gè)變量。這里可以使用whenComplete()來代替then,它與then的不同點(diǎn)在于哪怕設(shè)置變量出現(xiàn)了異常也會(huì)被調(diào)用到。這個(gè)有點(diǎn)像java中的finally。

如果上面這個(gè)使用變量也要花費(fèi)一段時(shí)間,那么可以考慮將其放入一個(gè)新的Future中:

future.then(...set an important variable...)
  .then((_) {new Future(() {...use the important variable...})});

使用一個(gè)新的Future可以給事件循環(huán)一個(gè)機(jī)會(huì)先去處理列隊(duì)中的其他事件。

怎么安排一個(gè)任務(wù)

??當(dāng)你需要指定一些代碼稍后運(yùn)行的時(shí)候,你可以使用dart:async提供的兩種方式:

1.Future類,它可以向event隊(duì)列的尾部添加一個(gè)事件。
2.使用頂級(jí)方法scheduleMicrotask(),它可以向microtask隊(duì)列的尾部添加一個(gè)微任務(wù)。

使用合理的隊(duì)列

??有可能的還是盡量使用Future來向event隊(duì)列添加事件。使用event隊(duì)列可以保持microtask隊(duì)列的簡(jiǎn)短,以此減少microtask的過度使用導(dǎo)致event隊(duì)列的堵塞。

??如果一個(gè)任務(wù)確實(shí)要在event隊(duì)列的任何一個(gè)事件前完成,那么你應(yīng)該盡量直接寫在main方法中而不是使用這兩個(gè)隊(duì)列。如果你不能那么就用scheduleMicrotask來向microtask添加一個(gè)微任務(wù)。

????
scheduleMicrotask_microtask

Event隊(duì)列

使用new Future或者new Future.delayed()來向event隊(duì)列中添加事件。

注意:你也可以使用Timer來安排任務(wù),但是使用Timer的過程中如果出現(xiàn)異常,則會(huì)退出程序。這里推薦使用Future,它是構(gòu)建在Timer之上并加入了更多的功能,比如檢測(cè)任務(wù)是否完成和異常反饋。

立刻需要將任務(wù)加入event隊(duì)列可以使用new Future

//向event隊(duì)列中添加一個(gè)任務(wù)
Future(() {
  //任務(wù)具體代碼
});

你也可以使用then或者whenComplete在Future結(jié)束后立刻執(zhí)行某段代碼。如下面這段代碼在這個(gè)Future被執(zhí)行后會(huì)立刻輸出42:

Future(() => 21)
.then((v) => v*2)
.then((v) => print(v));

如果要在一段時(shí)間后添加一個(gè)任務(wù),可以使用new Future.delayed():

// 一秒以后將任務(wù)添加至event隊(duì)列
Future.delayed(const Duration(seconds:1), () {
  //任務(wù)具體代碼
});

雖然上面這個(gè)例子中一秒后向event隊(duì)列添加一個(gè)任務(wù),但是這個(gè)任務(wù)想要被執(zhí)行的話必須滿足一下幾點(diǎn):

  • main方法執(zhí)行完畢
  • microtask隊(duì)列為空
  • 該任務(wù)前的任務(wù)全部執(zhí)行完畢
  • 所以該任務(wù)真正被執(zhí)行可能是大于1秒后。

關(guān)于Future的有趣事實(shí):

  1. 被添加到then()中的方法會(huì)在Future執(zhí)行后立馬執(zhí)行(這方法沒有被加入任何隊(duì)列,只是被回調(diào)了)。
  2. 如果在then()調(diào)用之前Future就已經(jīng)執(zhí)行完畢了,那么會(huì)有一個(gè)任務(wù)被加入到microtask隊(duì)列中。這個(gè)任務(wù)執(zhí)行的就是被傳入then的方法。
  3. Future()和Future.delayed()構(gòu)造方法并不會(huì)被立刻完成,他們會(huì)向event隊(duì)列中添加一個(gè)任務(wù)。
  4. Future.value()構(gòu)造方法會(huì)在一個(gè)microtask中完成。
  5. Future,sync()構(gòu)造方法會(huì)立馬執(zhí)行其參數(shù)方法,并在microtask中完成。

Microtask隊(duì)列: scheduleMicrotask()

dart:async定義了一個(gè)頂級(jí)方法scheduleMicrotask() ,你可以這樣使用:

scheduleMicrotask(() {
  // ...code goes here...
});

如果有必要可以使用isolate或worker

??如果你想要完成一些重量級(jí)的任務(wù),為了保證你應(yīng)用可響應(yīng),你應(yīng)該將任務(wù)添加到isolate或者worker中。isolate可能會(huì)運(yùn)行在不同的進(jìn)程或線程中.這取決于Dart的具體實(shí)現(xiàn)。

??那一般情況下你應(yīng)該使用多少個(gè)isolate來完成你的工作呢?通常情況下可以根據(jù)你的cpu的個(gè)數(shù)來決定。

??但你也可以使用超過cpu個(gè)數(shù)的isolate,前提是你的app能有一個(gè)好的架構(gòu)。讓不同的isolate來分擔(dān)不同的代碼塊運(yùn)行,但這前提是你能保證這些isolate之間沒有數(shù)據(jù)的共享。

測(cè)試一下你的理解程度

目前為止你已經(jīng)掌握了調(diào)度任務(wù)的基本知識(shí),下面來測(cè)試一下你的理解程度。

問題1

下面這段代碼的輸出是什么?

import 'dart:async';
void main() {
  print('main #1 of 2');
  scheduleMicrotask(() => print('microtask #1 of 2'));

  new Future.delayed(new Duration(seconds:1),
                     () => print('future #1 (delayed)'));
  new Future(() => print('future #2 of 3'));
  new Future(() => print('future #3 of 3'));

  scheduleMicrotask(() => print('microtask #2 of 2'));

  print('main #2 of 2');
}

別急著看答案,自己在紙上寫寫答案呢?

答案:

main #1 of 2
main #2 of 2
microtask #1 of 2
microtask #2 of 2
future #2 of 3
future #3 of 3
future #1 (delayed)     

上面的答案是否就是你所期望的呢?這段代碼一共執(zhí)行了三個(gè)分支:

  1. main()方法
  2. microtask隊(duì)列
  3. event隊(duì)列(先new Future后new Future.delayed)

??main方法中的普通代碼都是同步執(zhí)行的,所以肯定是main打印先全部打印出來,等main方法結(jié)束后會(huì)開始檢查microtask中是否有任務(wù),若有則執(zhí)行,執(zhí)行完繼續(xù)檢查microtask,直到microtask列隊(duì)為空。所以接著打印的應(yīng)該是microtask的打印。最后會(huì)去執(zhí)行event隊(duì)列。由于有一個(gè)使用的delay方法,所以它的打印應(yīng)該是在最后的。

問題2

下面這個(gè)問題相對(duì)有些復(fù)雜:

import 'dart:async';
void main() {
  print('main #1 of 2');
  scheduleMicrotask(() => print('microtask #1 of 3'));

  new Future.delayed(new Duration(seconds:1),
      () => print('future #1 (delayed)'));

  new Future(() => print('future #2 of 4'))
      .then((_) => print('future #2a'))
      .then((_) {
        print('future #2b');
        scheduleMicrotask(() => print('microtask #0 (from future #2b)'));
      })
      .then((_) => print('future #2c'));

  scheduleMicrotask(() => print('microtask #2 of 3'));

  new Future(() => print('future #3 of 4'))
      .then((_) => new Future(
                   () => print('future #3a (a new future)')))
      .then((_) => print('future #3b'));

  new Future(() => print('future #4 of 4'));
  scheduleMicrotask(() => print('microtask #3 of 3'));
  print('main #2 of 2');
}

答案:

main #1 of 2
main #2 of 2
microtask #1 of 3
microtask #2 of 3
microtask #3 of 3
future #2 of 4
future #2a
future #2b
future #2c
microtask #0 (from future #2b)
future #3 of 4
future #4 of 4
future #3a (a new future)
future #3b
future #1 (delayed)

總結(jié)

以下有幾點(diǎn)關(guān)于dart的事件循環(huán)機(jī)制需要牢記于心:

  1. Dart事件循環(huán)執(zhí)行兩個(gè)隊(duì)列里的事件:event隊(duì)列和microtask隊(duì)列。
  2. event隊(duì)列的事件來自dart(future,timer,isolate message等)和系統(tǒng)(用戶輸入,I/O等)。
  3. 目前為止,microtask隊(duì)列的事件只來自dart。
  4. 事件循環(huán)會(huì)優(yōu)先清空microtask隊(duì)列,然后才會(huì)去處理event隊(duì)列。
  5. 當(dāng)兩個(gè)隊(duì)列都清空后,dart就會(huì)退出。
  6. main方法,來自event隊(duì)列和microtask隊(duì)列的所有事件都運(yùn)行在Dart的main isolate中。

當(dāng)你要安排一個(gè)任務(wù)時(shí),請(qǐng)遵守以下規(guī)則:

  1. 如果可以,盡量將任務(wù)放入event隊(duì)列中。
  2. 使用Future的then方法或whenComplete方法來指定任務(wù)順序。
  3. 為了保持你app的可響應(yīng)性,盡量不要將大計(jì)算量的任務(wù)放入這兩個(gè)隊(duì)列。
  4. 大計(jì)算量的任務(wù)放入額外的isolate中。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容