← 開發日常

Dart 非同步介紹(二) - Future

在寫 Dart 的過程中,一定會碰到許多需要非同步的情境,例如:打 Web Api 從 Server 端讀寫資料、存取 local storage、sqlite...等等。Future 讓非同步操作用起來很容易,但若不清楚其執行邏輯,有些時候難免會造成一些 bug,而非同步的 bug 又比較難以除錯。所以今天就來用個簡單的例子,來聊聊 Future 的執行邏輯。

dart
void main() async {
  print("1");
  await loadData();
  print("4");
}

Future loadData() async {
  print("2");
  await Future.delayed(Duration(seconds:1), () => "data");
  print("3");
}

上面這段代碼執行結果是

dart
1
2
3
4

因為每一段非同步都用了await來等待,會等到結果回傳了才會往下一步前進。執行過程如下:

  1. 執行 print("1") 並印出 1
  2. 進入 loadData() 方法中
  3. 執行 print("2") 並印出 2
  4. 執行 Future.delayed() 並回傳 Future
  5. await 接到 Future.delayed 回傳的 Future 後,發現 Future 尚未執行完成,loadData() 也從 await 處回傳 Future,程式控制權從 loadData() 回到 main() 中
  6. 同理,main 方法中的 await 發現 loadData() 回傳的 Future 尚未執行完成,所以 main() 也從 await 處回傳 Future,程式控制權也從 main() 回到呼叫 main() 的 framework 中
  7. Future.delayed() 執行完成,並從 loadData() 中的 await 處繼續往下執行
  8. 執行 print("3") 並印出 3
  9. loadData() 執行完成,並回傳完成執行的 Future 到 main() 中
  10. 回到 main() 後,從 await 處往下執行
  11. 執行 print("4") 並印出 4

移除 main 中的 await

把呼叫 loadData() 時的 await拿掉以後並在執行一次

dart
void main() async {
  print("1");
  loadData();
  print("4");

	// 讓程式不要太快結束,避免來不及印出 3
  await Future.delayed(Duration(seconds: 5), () => {});
}

Future loadData() async {
  print("2");
  await Future.delayed(Duration(seconds:1), () => "data");
  print("3");
}

執行結果變成

dart
1
2
4
3

與第一個例子不同的是,當 loadData() 回傳未完成的 Future 後,main() 並沒有用 await 等待,所以程式就繼續往下執行,並印出 4。過了一秒之後,Future.delayed 完成並從 loadData() 的 await 處往下執行,並印出 3。

移除 loadData 中的 await

我們稍微修改一下例子,讓 main() 同樣 await loadData(),但是 loadData() 不 await Future.delayed()

dart
void main() async {
  print("1");
  await loadData();
  print("4");
}

Future loadData() async {
  print("2");
  Future.delayed(Duration(seconds:1), () => "data");
  print("3");
}

執行結果回到

dart
1
2
3
4

與第一個例子不同的是,當 loadData() 執行到 Future.delayed() 時,此處沒有使用 await 來中斷執行,而是繼續往下執行並印出 3。當loadData() 執行完 print("3") 回傳 Future 到 main() 中,此時 main() 中的 await 發現 Future 已經執行完成,所以也就已同步的方式往下執行並印出 4。

執行 await 後的工作

如同之前提到的 Dart 非同步會使用 queue 來安排工作,從 await 處往下執行的這項工作也同樣會被排進 queue 中,也就是說在當前工作未完成之前,即使 await 處的工作已經完成,也無法繼續往下執行,讓我們來看看另外一個例子

dart
void main() async {
  print("1");
  loadData();
  sleep(Duration(seconds: 2));
  print("4");

	// 讓程式不要太快結束,避免來不及印出 3
  await Future.delayed(Duration(seconds: 5), () => {});
}

Future loadData() async {
  print("2");
  await Future.delayed(Duration(seconds: 1), () => "data");
  print("3");
}

執行結果是

dart
1
2
4
3

執行完 main() 需要兩秒,而Future.delayed() 的工作只需要一秒,但是結果還是先印出4,再印出 3,因為一次只能有一項工作在執行,此時已經被 main() 方法佔住了,所以即便 Future.delayed() 已經執行完成,它也只能乖乖在 queue 中等待 main() 把 sleep() 和 print("4") 執行完,然後才輪到 print("3")。

小結

看了上面幾個例子之後,我們可以歸納一些結論

  1. 沒有 await 的 async 方法等於同步方法
  2. 當程式執行到 await 時,是否往下執行取決於當下 Future 的狀態
  3. 非同步的 Future 執行結束後會排入 queue 中等待執行