Dart 語言異步編程之Future

  • 2019 年 10 月 4 日
  • 筆記
  • Dart 異步編程
    • Dart 的事件循環
    • 調度任務
    • 延時任務
    • Future 詳解
      • 創建 Future
      • 註冊回調
    • async 和 await

Dart 異步編程

編程中的代碼執行,通常分為同步異步兩種。簡單說,同步就是按照代碼的編寫順序,從上到下依次執行,這也是最簡單的我們最常接觸的一種形式。但是同步代碼的缺點也顯而易見,如果其中某一行或幾行代碼非常耗時,那麼就會阻塞,使得後面的代碼不能被立刻執行。

異步的出現正是為了解決這種問題,它可以使某部分耗時代碼不在當前這條執行線路上立刻執行,那究竟怎麼執行呢?最常見的一種方案是使用多線程,也就相當於開闢另一條執行線,然後讓耗時代碼在另一條執行線上運行,這樣兩條執行線並列,耗時代碼自然也就不能阻塞主執行線上的代碼了。

多線程雖然好用,但是在大量並發時,仍然存在兩個較大的缺陷,一個是開闢線程比較耗費資源,線程開多了機器吃不消,另一個則是線程的鎖問題,多個線程操作共享內存時需要加鎖,複雜情況下的鎖競爭不僅會降低性能,還可能造成死鎖。因此又出現了基於事件的異步模型。簡單說就是在某個單線程中存在一個事件循環和一個事件隊列,事件循環不斷的從事件隊列中取出事件來執行,這裡的事件就好比是一段代碼,每當遇到耗時的事件時,事件循環不會停下來等待結果,它會跳過耗時事件,繼續執行其後的事件。當不耗時的事件都完成了,再來查看耗時事件的結果。因此,耗時事件不會阻塞整個事件循環,這讓它後面的事件也會有機會得到執行。

我們很容易發現,這種基於事件的異步模型,只適合I/O密集型的耗時操作,因為I/O耗時操作,往往是把時間浪費在等待對方傳送數據或者返回結果,因此這種異步模型往往用於網絡服務器並發。如果是計算密集型的操作,則應當儘可能利用處理器的多核,實現並行計算。

在這裡插入圖片描述

Dart 的事件循環

Dart 是事件驅動的體系結構,該結構基於具有單個事件循環和兩個隊列的單線程執行模型。Dart雖然提供調用堆棧。但是它使用事件在生產者和消費者之間傳輸上下文。事件循環由單個線程支持,因此根本不需要同步和鎖定。

Dart 的兩個隊列分別是

  • MicroTask queue 微任務隊列
  • Event queue 事件隊列

在這裡插入圖片描述

Dart事件循環執行如上圖所示

  1. 先查看MicroTask隊列是否為空,不是則先執行MicroTask隊列
  2. 一個MicroTask執行完後,檢查有沒有下一個MicroTask,直到MicroTask隊列為空,才去執行Event隊列
  3. Evnet 隊列取出一個事件處理完後,再次返回第一步,去檢查MicroTask隊列是否為空

我們可以看出,將任務加入到MicroTask中可以被儘快執行,但也需要注意,當事件循環在處理MicroTask隊列時,Event隊列會被卡住,應用程序無法處理鼠標單擊、I/O消息等等事件。

調度任務

注意,以下調用的方法,都定義在dart:async庫中。

將任務添加到MicroTask隊列有兩種方法

import  'dart:async';    void  myTask(){      print("this is my task");  }    void  main() {      // 1. 使用 scheduleMicrotask 方法添加      scheduleMicrotask(myTask);        // 2. 使用Future對象添加      new  Future.microtask(myTask);  }  

將任務添加到Event隊列

import  'dart:async';    void  myTask(){      print("this is my task");  }    void  main() {      new  Future(myTask);  }  

現在學會了調度任務,趕緊編寫代碼驗證以上的結論

import  'dart:async';    void  main() {      print("main start");        new  Future((){          print("this is my task");      });        new  Future.microtask((){          print("this is microtask");      });        print("main stop");  }  

運行結果:

main start  main stop  this is microtask  this is my task  

可以看到,代碼的運行順序並不是按照我們的編寫順序來的,將任務添加到隊列並不等於立刻執行,它們是異步執行的,當前main方法中的代碼執行完之後,才會去執行隊列中的任務,且MicroTask隊列運行在Event隊列之前。

延時任務

如需要將任務延伸執行,則可使用Future.delayed方法

new  Future.delayed(new  Duration(seconds:1),(){      print('task delayed');  });  

表示在延遲時間到了之後將任務加入到Event隊列。需要注意的是,這並不是準確的,萬一前面有很耗時的任務,那麼你的延遲任務不一定能準時運行。

import  'dart:async';  import  'dart:io';    void  main() {      print("main start");        new Future.delayed(new  Duration(seconds:1),(){          print('task delayed');      });        new Future((){          // 模擬耗時5秒          sleep(Duration(seconds:5));          print("5s task");      });        print("main stop");  }  

運行結果:

main start  main stop  5s task  task delayed  

從結果可以看出,delayed方法調用在前面,但是它顯然並未直接將任務加入Event隊列,而是需要等待1秒之後才會去將任務加入,但在這1秒之間,後面的new Future代碼直接將一個耗時任務加入到了Event隊列,這就直接導致寫在前面的delayed任務在1秒後只能被加入到耗時任務之後,只有當前面耗時任務完成後,它才有機會得到執行。這種機制使得延遲任務變得不太可靠,你無法確定延遲任務到底在延遲多久之後被執行。

Future 詳解

Future類是對未來結果的一個代理,它返回的並不是被調用的任務的返回值。

void  myTask(){      print("this is my task");  }    void  main() {      Future fut = new  Future(myTask);  }  

如上代碼,Future類實例fut並不是函數myTask的返回值,它只是代理了myTask函數,封裝了該任務的執行狀態。

創建 Future

Future的幾種創建方法

  • Future()
  • Future.microtask()
  • Future.sync()
  • Future.value()
  • Future.delayed()
  • Future.error()

其中sync是同步方法,任務會被立即執行

import  'dart:async';    void  main() {      print("main start");    new  Future.sync((){      print("sync task");  });    new  Future((){      print("async task");  });        print("main stop");  }  

運行結果:

main start  sync task  main stop  async task  

註冊回調

Future中的任務完成後,我們往往需要一個回調,這個回調立即執行,不會被添加到事件隊列。

import 'dart:async';    void main() {    print("main start");        Future fut =new Future.value(18);    // 使用then註冊回調    fut.then((res){      print(res);    });     // 鏈式調用,可以跟多個then,註冊多個回調    new Future((){      print("async task");    }).then((res){      print("async task complete");    }).then((res){      print("async task after");    });      print("main stop");  }  

運行結果:

main start  main stop  18  async task  async task complete  async task after  

除了then方法,還可以使用catchError來處理異常,如下

  new Future((){      print("async task");    }).then((res){      print("async task complete");    }).catchError((e){      print(e);    });  

還可以使用靜態方法wait 等待多個任務全部完成後回調。

import 'dart:async';    void main() {    print("main start");      Future task1 = new Future((){      print("task 1");      return 1;    });      Future task2 = new Future((){      print("task 2");      return 2;    });      Future task3 = new Future((){      print("task 3");      return 3;    });      Future fut = Future.wait([task1, task2, task3]);    fut.then((responses){      print(responses);    });      print("main stop");  }  

運行結果:

main start  main stop  task 1  task 2  task 3  [1, 2, 3]  

如上,wait返回一個新的Future,當添加的所有Future完成時,在新的Future註冊的回調將被執行。

async 和 await

在Dart1.9中加入了asyncawait關鍵字,有了這兩個關鍵字,我們可以更簡潔的編寫異步代碼,而不需要調用Future相關的API

async 關鍵字作為方法聲明的後綴時,具有如下意義

  • 被修飾的方法會將一個 Future 對象作為返回值
  • 該方法會同步執行其中的方法的代碼直到第一個 await 關鍵字,然後它暫停該方法其他部分的執行;
  • 一旦由 await 關鍵字引用的 Future 任務執行完成,await的下一行代碼將立即執行。
// 導入io庫,調用sleep函數  import 'dart:io';    // 模擬耗時操作,調用sleep函數睡眠2秒  doTask() async{    await sleep(const Duration(seconds:2));    return "Ok";  }    // 定義一個函數用於包裝  test() async {    var r = await doTask();    print(r);  }    void main(){    print("main start");    test();    print("main end");  }  

運行結果:

main start  main end  Ok  

需要注意,async 不是並行執行,它是遵循Dart 事件循環規則來執行的,它僅僅是一個語法糖,簡化Future API的使用。