關於Dart中Future的一些理解

zcchongpangzi發表於2020-03-17

最近在看Flutter,在實現網路請求時看到了Future,本以為和JS中的Promise一樣,用起來還是有些不同的,網上的文章講的不是很詳細,自己就蒐集了一些資料,下面是我自己的一些總結,幫助一些像我一樣迷惑的人

Dart中的執行緒

Dart是單執行緒的,是可以非同步的,Event Loop機制,iOS中叫runLoop,執行緒在大部分時間在等待被喚醒去做某些事情,在需要去響應某些操作的時候再去處理事情,例如使用者的點選事件,網路請求的返回處理等等

非同步任務

Dart中有兩個佇列,一個是EventQueue(事件佇列) 另一個是MicrotaskQueue(微任務佇列),Dart的Event Loop機制會不斷的輪詢事件佇列,在每次的輪詢中,Dart會先處理MicrotaskQueue,再處理EventQueue,這就產生了一個優先順序的區別,微任務享有最高的優先順序,微任務的通過scheduleMicrotask建立

scheduleMicrotask(() => print('This is a microtask'));
複製程式碼

但是,直接用到微任務佇列的情況很少(你要是想用也沒人攔著);非同步任務更多的情況是事件佇列來完成,Dart為EventQueue做了封裝,就是Future,用法如下

Future(() => print('Running in Future 1'));//下一個事件迴圈輸出字串

Future(() => print(‘Running in Future 2'))
  .then((_) => print('and then 1'))
  .then((_) => print('and then 2’));//上一個事件迴圈結束後,連續輸出三段字串
複製程式碼

熟悉的配方,熟悉的感覺。。。是不是和Promise很像,但還是有些區別,這個時候就會有疑問,這個then是什麼時候呼叫的?(Promise中可以主動呼叫resolve去觸發)當我們宣告一個Future時,Dart會將非同步任務的函式執行體放入事件佇列,然後返回,接著同步執行後續的程式碼;當同步執行完成後,事件佇列會按照宣告同步執行Future的函式體和後續的then 這裡還有個特殊情況,就是當Future的執行體返回值為null時後面又有個.then時,Dart就會把then放入微任務佇列,示例程式碼如下

//f1比f2先執行
Future(() => print('f1'));
Future(() => print('f2'));

//f3執行後會立刻同步執行then 3
Future(() => print('f3')).then((_) => print('then 3'));

//then 4會加入微任務佇列,儘快執行
Future(() => null).then((_) => print('then 4'));
複製程式碼

then會在Future函式體執行完畢後立刻執行,再看個比較複雜的例子


Future(() => print('f1'));//宣告一個匿名Future
Future fx = Future(() =>  null);//宣告Future fx,其執行體為null

//宣告一個匿名Future,並註冊了兩個then。在第一個then回撥裡啟動了一個微任務
Future(() => print('f2')).then((_) {
  print('f3');
  scheduleMicrotask(() => print('f4'));
}).then((_) => print('f5'));

//宣告瞭一個匿名Future,並註冊了兩個then。第一個then是一個Future
Future(() => print('f6'))
  .then((_) => Future(() => print('f7')))
  .then((_) => print('f8'));

//宣告瞭一個匿名Future
Future(() => print('f9'));

//往執行體為null的fx註冊了了一個then
fx.then((_) => print('f10'));

//啟動一個微任務
scheduleMicrotask(() => print('f11'));
print('f12');
複製程式碼

列印順序為 f12 f11 f1 f10 f2 f3 f5 f4 f6 f9 f7 f8 把這個例子弄懂了Future是怎麼使用應該就會了;

因為其他語句都是非同步任務,所以先列印 f12。剩下的非同步任務中,微任務佇列優先順序最高,因此隨後列印 f11;然後按照 Future 宣告的先後順序,列印 f1。隨後到了 fx,由於 fx 的執行體是 null,相當於執行完畢了,Dart 將 fx 的 then 放入微任務佇列,由於微任務佇列的優先順序最高,因此 fx 的 then 還是會最先執行,列印 f10。然後到了 fx 下面的 f2,列印 f2,然後執行 then,列印 f3。f4 是一個微任務,要到下一個事件迴圈才執行,因此後續的 then 繼續同步執行,列印 f5。本次事件迴圈結束,下一個事件迴圈取出 f4 這個微任務,列印 f4。然後到了 f2 下面的 f6,列印 f6,然後執行 then。這裡需要注意的是,這個 then 是一個 Future 非同步任務,因此這個 then,以及後續的 then 都被放入到事件佇列中了。f6 下面還有 f9,列印 f9。最後一個事件迴圈,列印 f7,以及後續的 f8。

非同步函式

關於Future的處理有兩種方式,使用.then,等Future的執行體結束了再非同步處理;或者使用async和await來同步等待執行體完成,下面是使用方法

//宣告瞭一個延遲3秒返回Hello的Future,並註冊了一個then返回拼接後的Hello 2020
Future<String> fetchContent() => 
  Future<String>.delayed(Duration(seconds:3), () => "Hello")
    .then((x) => "$x 2020");

  main() async{
    print(await fetchContent());//等待Hello 2020的返回
  }
複製程式碼

使用async關鍵字的函式也是一個非同步函式,這個await類似js中的async,await;看下面的程式碼

Future(() => print('f1'))
  .then((_) async {
    print('f5');
    await Future(() => print('f2'));
  })
  .then((_) => print('f3'));
Future(() => print('f4'));
複製程式碼

列印出來的結果是 f1、f5、f4、f2、f3;先列印f1,之後同步執行.then;但是.then中的執行體使用了async,它是個非同步函式,先執行f5,之後f2被放入了事件佇列,所以首先列印f4,之後是f2,最後是f3; 本文參考了極客時間的Flutter核心技術與實戰 time.geekbang.org/column/intr… 這個是我目前在網上找到的對Future解釋最清楚的了,有些不對的地方還請大佬指正

相關文章