Dart中的異步編程——Future、async和await

首先Dart是一門單線程的語言,那麼Dart異步操作對支持,可以使我們在編寫Dart程序時可以異步的來執行耗時操作。從而可以在等待一個操作完成的同時進行別的操作以下是一些常見的異步操作:

  • 通過網絡獲取數據。

  • 寫入數據庫。

  • 從文件讀取數據。

要在Dart中執行異步操作,可以使用Future類和asyncawait關鍵字。

 

# Dart的事件循環(event loop)

Dart中,實際上有兩種隊列:

  1. 事件隊列(event queue),包含所有的外來事件:I/Omouse eventsdrawing eventstimersisolate之間的信息傳遞。

  2. 微任務隊列(microtask queue),表示一個短時間內就會完成的異步任務。它的優先級最高,高於event queue,只要隊列中還有任務,就可以一直霸佔着事件循環。microtask queue添加的任務主要是由 Dart內部產生。

因爲 microtask queue 的優先級高於 event queue ,所以如果 microtask queue有太多的微任務, 那麼就可能會霸佔住當前的event loop。從而對event queue中的觸摸、繪製等外部事件造成阻塞卡頓。

在每一次事件循環中,Dart總是先去第一個microtask queue中查詢是否有可執行的任務,如果沒有,纔會處理後續的event queue的流程。

異步任務我們用的最多的還是優先級更低的 event queueDart爲 event queue 的任務建立提供了一層封裝,就是我們在Dart中經常用到的Future

正常情況下,一個 Future 異步任務的執行是相對簡單的:

 

  1. 聲明一個 Future 時,Dart 會將異步任務的函數執行體放入event queue,然後立即返回,後續的代碼繼續同步執行。

  2. 當同步執行的代碼執行完畢後,event queue會按照加入event queue的順序(即聲明順序),依次取出事件,最後同步執行 Future 的函數體及後續的操作。

# Future

Future<T> 類,其表示一個 T 類型的異步操作結果。如果異步操作不需要結果,則類型爲 Future<void>。也就是說首先Future是個泛型類,可以指定類型。如果沒有指定相應類型的話,則Future會在執行動態的推導類型。

# Future基本用法

# Future工廠構造函數

什麼是工廠構造函數

工廠構造函數是一種構造函數,與普通構造函數不同,工廠函數不會自動生成實例,而是通過代碼來決定返回的實例對象。
Dart中,工廠構造函數的關鍵字爲factory。我們知道,構造函數包含類名構造函數和命名構造方法,在構造方法前加上factory之後變成了工廠構造函數。也就是說factory可以放在類名函數之前,也可以放在命名函數之前。

下面我們通過Future的工廠構造函數,創建一個最簡單的Future

可以看到,Future的工廠構造函數接收一個Dart函數作爲參數。這個函數沒有參數,返回值是FutureOr<T>類型。

從打印結果可以看出,Future不需要結果時,返回的類型是 Future<void>

注意,是先執行的類型判斷,後打印的Future內的操作。

asyncawait

默認的Future是異步運行的。如果想要我們的Future同步執行,可以通過asyncawait關鍵字:

可以看到,我們的Future已經同步執行了。await會等待Future執行結束後,纔會繼續執行後面的代碼。

關鍵字asyncawaitDart語言異步支持的一部分。

異步函數即在函數頭中包含關鍵字async的函數。

 

async:用來表示函數是異步的,定義的函數會返回一個Future對象。

await:後面跟着一個Future,表示等待該異步任務完成,異步任務完成後纔會繼續往下執行。await只能出現在異步函數內部。能夠讓我們可以像寫同步代碼那樣來執行異步任務而不使用回調的方式。

在執行完打印後,會開始檢查microtask queue中是否有任務,若有則執行,直到microtask queue列隊爲空。因爲microtask queue的優先級是最高的。然後再去執行event queue。一般Future創建的事件會插入event queue順序執行(使用Future.microtask方法例外)。

注意:在Dart中,async/await都只是一個語法糖,編譯器或解釋器最終都會將其轉化爲一個Promise(Future)的調用鏈。
在Dart 2.0之前,async函數會立即返回,而無需在async函數體內執行任何代碼
所以,如果我們將代碼改成下面的這種形式:

當我們使用了async關鍵字,意味着testFuture函數已經變成了異步函數。

所以會先執行testFuture函數之後的打印。

 

在執行完打印後,會開始檢查microtask queue中是否有任務,若有則執行,直到microtask queue列隊爲空。因爲microtask queue的優先級是最高的。然後再去執行event queue。一般Future創建的事件會插入event queue順序執行(使用Future.microtask方法例外)。

# Future.value()

創建一個返回指定value值的Future

void testFuture() async {
    var future = await Future.value(1);
    print("future value: $future.");
}
testFuture();
print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
future value: 1.

# Future.delayed()

創建一個延遲執行的Future:

void testFuture() async {
  Future.delayed(Duration(seconds: 2), () {
    print("Future.delayed 2 seconds.");
  });
}

testFuture();
print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
Future.delayed 2 seconds.

Future簡單使用

# 處理Future的結果

對於Future來說,異步處理成功了就執行成功的操作,異步處理失敗了就捕獲錯誤或者停止後續操作。一個Future只會對應一個結果,要麼成功,要麼失敗。

請記住,Future的所有API的返回值仍然是一個Future對象,所以可以很方便的進行鏈式調用

Dart提供了下面三個方法用來處理Future的結果。

Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
Future<T> catchError(Function onError, {bool test(Object error)});
Future<T> whenComplete(FutureOr action());

# Future.then()

用來註冊一個Future完成時要調用的回調。如果 Future 有多個then,它們也會按照鏈接的先後順序同步執行,同時也會共用一個event loop

void testFuture() async {
    Future.value(1).then((value) {
      return Future.value(value + 2);
    }).then((value) {
      return Future.value(value + 3);
    }).then(print);
}
testFuture();
  
print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
6

同時,then()會在 Future函數體執行完畢後立刻執行:

void testFuture() async {
     var future = new Future.value('a').then((v1) {
      return new Future.value('$v1 b').then((v2) {
        return new Future.value('$v2 c').then((v3) {
          return new Future.value('$v3 d');
        });
      });
    });
    future.then(print, onError: print);
}
testFuture();
  
print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
a b c d

那麼問題來了,如果Future已經執行完畢了,我們再來獲取到這個Future的引用,然後再繼續調用then()方法。那麼此時,Dart會如何處理這種情況?對於這種情況,Dart會將後續加入的then()方法體放入microtask queue,儘快執行:

 

  1. 因爲先調用的testFuture()函數,所以先打印future 13

  2. 再執行testFuture()後面的打印。

  3. 開始異步任務執行。

  4. 首先執行優先級最高的microtask queue任務scheduleMicrotask,打印future 12

  5. 然後按照Future的聲明順序再執行,打印future 1.

  6. 然後到了 futureFinish,打印future 2。此時futureFinish已經執行完畢。所以Dart會將後續通過futureFinish 調用的 then方法放入microtask queue。由於microtask queue的優先級最高。因此 futureFinish 的 then 會最先執行,打印 future 11

  7. 然後繼續執行event queue裏面的future 3。然後執行 then,打印 future 4。同時在then方法裏向microtask queue裏添加了一個微任務。因爲此時正在執行的是event queue,所以要等到下一個事件循環才能執行。因此後續的 then 繼續同步執行,打印 future 6。本次事件循環結束,下一個事件循環取出 future 5 這個微任務,打印 future 5

  8. microtask queue任務執行完畢。繼續執行event queue裏的任務。打印 future 7。然後執行 then這裏需要注意的是:此時的 then 返回的是一個新創建的 Future 。因此這個 then,以及後續的 then 都將被被添加到event queue中了。

  9. 按照順序繼續執行evnet queue裏面的任務,打印 future 10

  10. 最後一個事件循環,取出evnet queue裏面通過future 7then方法新加入的 future 8,以及後續的 future 9,打印。

  11. 整個過程結束。

# Future.catchError

註冊一個回調,來捕捉Futureerror

void testFuture() async {
  new Future.error('Future 發生錯誤啦!').catchError(print, test: (error) {
    return error is String;
  });
}

testFuture();

print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
Future 發生錯誤啦!

then中的回調onErrorFuture.catchError

Future.catchError回調只處理原始Future拋出的錯誤,不能處理回調函數拋出的錯誤,onError只能處理當前Future的錯誤:

void testFuture() async {
    new Future.error('Future 發生錯誤啦!').then((_) {
       throw 'new error';
    }).catchError((error) {
      print('error: $error');
      throw 'new error2';
    }).then(print, onError:(error) {
      print("handle new error: $error");
    });
}
testFuture();
  
print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
error: Future 發生錯誤啦!
handle new error: new error2

# Future.whenComplete

Future.whenComplete 在Future完成之後總是會調用,不管是錯誤導致的完成還是正常執行完畢。比如在網絡請求前彈出加載對話框,在請求結束後關閉對話框。並且返回一個Future對象:

void testFuture() async {
    var random = new Random();
    new Future.delayed(new Duration(seconds: 1), () {
        if (random.nextBool()) {
          return 'Future 正常';
        } else {
          throw 'Future 發生錯誤啦!';
        }
    }).then(print).catchError(print).whenComplete(() {
        print('Future whenComplete!');
    });
}
testFuture();
  
print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
Future 發生錯誤啦!
Future whenComplete!
在testFuture()執行之後打印。
Future 正常
Future whenComplete!

# Future高級用法

# Future.timeout

本來Future會在2s後完成,但是timeout聲明的是1s後超時,所以1s後Future會拋出TimeoutException

void testFuture() async {
    new Future.delayed(new Duration(seconds: 2), () {
      return 1;
    }).timeout(new Duration(seconds:1)).then(print).catchError(print);
}
testFuture();
  
print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
TimeoutException after 0:00:01.000000: Future not completed

# Future.foreach

根據某個集合對象,創建一系列的Future。並且會按順序執行這些Future。例如,根據{1,2,3}創建3個延遲對應秒數的Future。執行結果爲1秒後打印1,再過2秒打印2,再過3秒打印3,總時間爲6秒:

void testFuture() async {
    Future.forEach({1,2,3}, (num){
      return Future.delayed(Duration(seconds: num),(){print("第$num秒執行");});
    });
}
testFuture();
  
print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
第1秒執行
第2秒執行
第3秒執行

# Future.wait

等待多個Future完成,並收集它們的結果。有兩種情況:

 

所有Future都有正常結果返回。則Future的返回結果是所有指定Future的結果的集合:

void testFuture() async {
    var future1 = new Future.delayed(new Duration(seconds: 1), () => 1);

    var future2 =

    new Future.delayed(new Duration(seconds: 2), ()  => 2);

    var future3 = new Future.delayed(new Duration(seconds: 3), () => 3);

    Future.wait({future1,future2,future3}).then(print).catchError(print);

}

testFuture();

print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
[1, 2, 3]

其中一個或者幾個Future發生錯誤,產生了error。則Future的返回結果是第一個發生錯誤的Future的值:

void testFuture() async {
    var future1 = new Future.delayed(new Duration(seconds: 1), () => 1);

    var future2 =

    new Future.delayed(new Duration(seconds: 2), () {

      throw 'Future 發生錯誤啦!';

    });

    var future3 = new Future.delayed(new Duration(seconds: 3), () => 3);

    Future.wait({future1,future2,future3}).then(print).catchError(print);

}

testFuture();

print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
Future 發生錯誤啦!

通過這篇文章我們瞭解了Dart中的事件循環和event queuemicrotask queue之間關係。同時,介紹了一些關於Dart Future的一些基礎使用和高級用法,同時穿插了一些使用實例,用來幫助大家更好的來理解Dart中的異步操作。當然,還有一些關於Dart異步編程和多線程的一些知識,這裏沒有過多的涉及。會在後續的文章來繼續給大家來講解。

# Future.any

返回的是第一個執行完成的Future的結果,不會管這個結果是正確的還是error

  void testFuture() async {
    Future
    .any([1, 2, 5].map((delay) => new Future.delayed(new Duration(seconds: delay), () => delay)))
    .then(print)
    .catchError(print);
  }
  testFuture();
  
  print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
1

# Future.doWhile

重複性地執行某一個動作,直到返回false或者Future,退出循環,適用於一些需要遞歸操作的場景:

void testFuture() async {
    var random = new Random();
    var totalDelay = 0;
    Future.doWhile(() {
        if (totalDelay > 10) {
          print('total delay: $totalDelay seconds');
          return false;
        }
        var delay = random.nextInt(5) + 1;
        totalDelay += delay;
        return new Future.delayed(new Duration(seconds: delay), () {
          print('waited $delay seconds');
          return true;
        });
    }).then(print).catchError(print);
}
testFuture();
  
print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
waited 5 seconds
waited 5 seconds
waited 3 seconds
total delay: 11 seconds
null
waited 4 seconds
waited 3 seconds
total delay: 12 seconds
null

# Future.sync

會同步執行其入參函數,然後調度到microtask queue來完成自己。也就是一個阻塞任務,會阻塞當前代碼,sync的任務執行完了,代碼才能走到下一行:

void testFuture() async {
    Future((){
        print("Future event 1");
    });
    Future.sync(() {
        print("Future sync microtask event 2");
    });
    Future((){
        print("Future event 3");
    });
    Future.microtask((){
        print("microtask event");
    });
}
testFuture();
print("在testFuture()執行之後打印。");

執行結果:

Future sync microtask event 2
在testFuture()執行之後打印。
microtask event
Future event 1
Future event 3

但是注意,如果這個入參函數返回一個Future

void testFuture() async {
    Future((){
        print("Future event 1");
    });
    Future.sync(() {
        return Future(() {  print("Future event 2");
      });
    });
    Future((){
        print("Future event 3");
    });
    Future.microtask((){
        print("microtask event");
    });
}
testFuture();
print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
microtask event
Future event 1
Future event 2
Future event 3

Future.microtask

創建一個在microtask queue運行的Future。我們知道microtask queue的優先級是比event queue高的。而一般Future是在event queue執行的。所以Future.microtask創建的Future會優先於其他Future執行:

void testFuture() async {
    Future((){
        print("Future event 1");
    });
    Future((){
        print("Future event 2");
    });
    Future.microtask((){
        print("microtask event");
    });
}
testFuture();
print("在testFuture()執行之後打印。");

執行結果:

在testFuture()執行之後打印。
microtask event
Future event 1
Future event 2

# 寫在最後

通過這篇文章我們瞭解了Dart中的事件循環和event queuemicrotask queue之間關係。同時,介紹了一些關於Dart Future的一些基礎使用和高級用法,同時穿插了一些使用實例,用來幫助大家更好的來理解Dart中的異步操作。當然,還有一些關於Dart異步編程和多線程的一些知識,這裏沒有過多的涉及。會在後續的文章來繼續給大家來講解。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章