【Dart學習】-- Dart之消息循環機制[翻譯]

概述 

  異步任務在Dart中隨處可見,例如許多庫的方法調用都會返回Future對象來實現異步處理,咱們也能夠註冊Handler來響應一些事件,如:鼠標點擊事件,I/O流結束和定時器到期。java

  這篇文章主要介紹了Dart中與異步任務相關的消息循環機制,閱讀完這篇文章後相信你可寫出更讚的異步執行代碼。你也能學習到如何調度Future任務而且預測他們的執行順序。api

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

基本概念

  若是你寫過一些關於UI的代碼,你就應該熟悉消息循環和消息隊列。有了他們才能保重UI的繪製操做和一些UI事件,如鼠標點擊事件能夠被一個一個的執行從而保證UI和UI事件的統一性。app

消息循環和消息隊列

  一個消息循環的職責就是不斷從消息隊列中取出消息並處理他們直到消息隊列爲空。異步

               
 
 

  消息隊列中的消息可能來自用戶輸入,文件I/O消息,定時器等。例以下圖的消息隊列就包含了定時器消息和用戶輸入消息。async

                          
 
 

  上述的這些概念你可能已經得心應手了,那接下來咱們就討論一下這些概念在Dart中是怎麼表現的?oop

Dart的單線程執行

  當一個Dart的方法開始執行時,他會一直執行直至達到這個方法的退出點。換句話說Dart的方法是不會被其餘Dart代碼打斷的。學習

Note:一個Dart的命令行應用能夠經過建立isolates來達到並行運行的目的。isolates之間不會共享內存,它們就像幾個運行在不一樣進程中的app,中能經過傳遞message來進行交流。出了明確指出運行在額外的isolates或者workers中的代碼外,全部的應用代碼都是運行在應用的main isolate中。要了解更多相關內容,能夠查看https://www.dartlang.org/articles/event-loop/#use-isolates-or-workers-if-necessary測試

  正以下圖所示,當一個Dart應用開始的標誌是它的main isolate執行了main方法。當main方法退出後,main isolate的線程就會去逐一處理消息隊列中的消息。ui

  
 
 

  事實上,上圖是通過簡化的流程。

Dart的消息循環和消息隊列

  一個Dart應用有一個消息循環和兩個消息隊列-- event隊列microtask隊列

  • event隊列包含全部外來的事件:I/O,mouse events,drawing events,timers,isolate之間的message等。
  • microtask 隊列在Dart中是必要的,由於有時候事件處理想要在稍後完成一些任務但又但願是在執行下一個事件消息以前。

  event隊列包含Dart和來自系統其它位置的事件。但microtask隊列只包含來自當前isolate的內部代碼。

  正以下面的流程圖,當main方法退出後,event循環就開始它的工做。首先它會以FIFO的順序執行micro task,當全部micro task執行完後它會從event 隊列中取事件並執行。如此反覆,直到兩個隊列都爲空。 
  

注意:當事件循環正在處理micro task的時候。event隊列會被堵塞。這時候app就沒法進行UI繪製,響應鼠標事件和I/O等事件

  雖然你能夠預測任務執行的順序,但你沒法準確的預測到事件循環什麼時候會處理你指望的任務。例如當你建立一個延時1s的任務,但在排在你以前的任務結束前事件循環是不會處理這個延時任務的,也就是或任務執行多是大於1s的。

經過連接的方式指定任務順序

  若是你的代碼之間存在依賴,那麼儘可能讓他們之間的依賴關係明確一點。明確的依賴關係能夠很好的幫助其餘開發者理解你的代碼,而且可讓你的代碼更穩定也更容易重構。

  先來看看下面這段

  • 錯誤的寫法:
    // 這樣寫錯誤的緣由就是沒有明確體現出設置變量和使用變量之間的依賴關係
    future.then(...set an important variable...);
    Timer.run(() {...use the important variable...});

     

  • 正確的寫法:

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

  爲了表示明確的先後依賴關係,咱們可使用then()()來代表要使用變量就必需要等設置完這個變量。這裏可使用whenComplete()來代替then,它與then的不一樣點在於哪怕設置變量出現了異常也會被調用到。這個有點像java中的finally。

  若是上面這個使用變量也要花費一段時間,那麼能夠考慮將其放入一個新的Future中:

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

  使用一個新的Future能夠給事件循環一個機會先去處理列隊中的其餘事件。

怎麼安排一個任務

  當你須要指定一些代碼稍後運行的時候,你可使用dart:async提供的兩種方式:

1.Future類,它能夠向event隊列的尾部添加一個事件。
2.使用頂級方法**scheduleMicrotask()**,它能夠向microtask隊列的尾部添加一個微任務。
  • 使用合理的隊列
    有可能的仍是儘可能使用Future來向event隊列添加事件。使用event隊列能夠保持microtask隊列的簡短,以此減小microtask的過分使用致使event隊列的堵塞。
    若是一個任務確實要在event隊列的任何一個事件前完成,那麼你應該儘可能直接寫在main方法中而不是使用這兩個隊列。若是你不能那麼就用scheduleMicrotask來向microtask添加一個微任務。

 

  • Event隊列

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

    注意:你也可使用Timer來安排任務,可是使用Timer的過程當中若是出現異常,則會退出程序。這裏推薦使用Future,它是構建在Timer之上並加入了更多的功能,好比檢測任務是否完成和異常反饋。

    馬上須要將任務加入event隊列可使用new Future

    //向event隊列中添加一個任務
    new Future(() {
      //任務具體代碼
    });

    你也可使用then或者whenComplete在Future結束後馬上執行某段代碼。以下面這段代碼在這個Future被執行後會馬上輸出42:

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

    若是要在一段時間後添加一個任務,可使用new Future.delayed():

    // 一秒之後將任務添加至event隊列
    new Future.delayed(const Duration(seconds:1), () {
      //任務具體代碼
    });

    雖然上面這個例子中一秒後向event隊列添加一個任務,可是這個任務想要被執行的話必須知足一下幾點:

    1. main方法執行完畢
    2. microtask隊列爲空
    3. 該任務前的任務所有執行完畢
      因此該任務真正被執行多是大於1秒後。 

  關於Future的有趣事實:

(1)被添加到then()中的方法會在Future執行後立馬執行(這方法沒有被加入任何隊列,只是被回調了)。
(2)若是在then()調用以前Future就已經執行完畢了,那麼會有一個任務被加入到microtask隊列中。這個任務執行的就是被傳入then的方法。
(3)Future()和Future.delayed()構造方法並不會被馬上完成,他們會向event隊列中添加一個任務。
(4)Future.value()構造方法會在一個microtask中完成。
(5)Future,sync()構造方法會立馬執行其參數方法,並在microtask中完成。

Microtask隊列: scheduleMicrotask()

  dart:async定義了一個頂級方法scheduleMicrotask() ,你能夠這樣使用:

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

若是有必要可使用isolate或worker

  若是你想要完成一些重量級的任務,爲了保證你應用可響應,你應該將任務添加到isolate或者worker中。isolate可能會運行在不一樣的進程或線程中.這取決於Dart的具體實現。

  那通常狀況下你應該使用多少個isolate來完成你的工做呢?一般狀況下能夠根據你的cpu的個數來決定。

  但你也可使用超過cpu個數的isolate,前提是你的app能有一個好的架構。讓不一樣的isolate來分擔不一樣的代碼塊運行,但這前提是你能保證這些isolate之間沒有數據的共享。

測試一下你的理解程度

  目前爲止你已經掌握了調度任務的基本知識,下面來測試一下你的理解程度。

問題1

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

import 'dart:async';
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)

上面的答案是否就是你所指望的呢?這段代碼一共執行了三個分支:

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

main方法中的普通代碼都是同步執行的,因此確定是main打印先所有打印出來,等main方法結束後會開始檢查microtask中是否有任務,如有則執行,執行完繼續檢查microtask,直到microtask列隊爲空。因此接着打印的應該是microtask的打印。最後會去執行event隊列。因爲有一個使用的delay方法,因此它的打印應該是在最後的。

問題2

下面這個問題相對有些複雜:

import 'dart:async';
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)

總結

如下有幾點關於dart的事件循環機制須要牢記於心:

  • Dart事件循環執行兩個隊列裏的事件:event隊列和microtask隊列。
  • event隊列的事件來自dart(future,timer,isolate message等)和系統(用戶輸入,I/O等)。
  • 目前爲止,microtask隊列的事件只來自dart。
  • 事件循環會優先清空microtask隊列,而後纔會去處理event隊列。
  • 當兩個隊列都清空後,dart就會退出。
  • main方法,來自event隊列和microtask隊列的全部事件都運行在Dart的main isolate中。

當你要安排一個任務時,請遵照如下規則:

  • 若是能夠,儘可能將任務放入event隊列中。
  • 使用Future的then方法或whenComplete方法來指定任務順序。
  • 爲了保持你app的可響應性,儘可能不要將大計算量的任務放入這兩個隊列。
  • 大計算量的任務放入額外的isolate中。
相關文章
相關標籤/搜索