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ì)列為空。
????
??消息隊(duì)列中的消息可能來自用戶輸入,文件I/O消息,定時(shí)器等。例如下圖的消息隊(duì)列就包含了定時(shí)器消息和用戶輸入消息。
?????
??上述的這些概念你可能已經(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ì)列中的消息
?????
事實(shí)上,上圖是經(jīng)過簡(jiǎn)化的流程。
Dart的消息循環(huán)和消息隊(duì)列
一個(gè)Dart應(yīng)用有一個(gè)消息循環(huán)和兩個(gè)消息隊(duì)列event隊(duì)列和microtask隊(duì)列。
event隊(duì)列包含所有外來的事件:I/O,mouse events,drawing events,timers,isolate之間的message等。
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ì)列都為空。
??????
注意:當(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ù)。
????
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í):
- 被添加到then()中的方法會(huì)在Future執(zhí)行后立馬執(zhí)行(這方法沒有被加入任何隊(duì)列,只是被回調(diào)了)。
- 如果在then()調(diào)用之前Future就已經(jīng)執(zhí)行完畢了,那么會(huì)有一個(gè)任務(wù)被加入到microtask隊(duì)列中。這個(gè)任務(wù)執(zhí)行的就是被傳入then的方法。
- Future()和Future.delayed()構(gòu)造方法并不會(huì)被立刻完成,他們會(huì)向event隊(duì)列中添加一個(gè)任務(wù)。
- Future.value()構(gòu)造方法會(huì)在一個(gè)microtask中完成。
- 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è)分支:
- main()方法
- microtask隊(duì)列
- 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ī)制需要牢記于心:
- Dart事件循環(huán)執(zhí)行兩個(gè)隊(duì)列里的事件:event隊(duì)列和microtask隊(duì)列。
- event隊(duì)列的事件來自dart(future,timer,isolate message等)和系統(tǒng)(用戶輸入,I/O等)。
- 目前為止,microtask隊(duì)列的事件只來自dart。
- 事件循環(huán)會(huì)優(yōu)先清空microtask隊(duì)列,然后才會(huì)去處理event隊(duì)列。
- 當(dāng)兩個(gè)隊(duì)列都清空后,dart就會(huì)退出。
- main方法,來自event隊(duì)列和microtask隊(duì)列的所有事件都運(yùn)行在Dart的main isolate中。
當(dāng)你要安排一個(gè)任務(wù)時(shí),請(qǐng)遵守以下規(guī)則:
- 如果可以,盡量將任務(wù)放入event隊(duì)列中。
- 使用Future的then方法或whenComplete方法來指定任務(wù)順序。
- 為了保持你app的可響應(yīng)性,盡量不要將大計(jì)算量的任務(wù)放入這兩個(gè)隊(duì)列。
- 大計(jì)算量的任務(wù)放入額外的isolate中。