跟我學flutter:我們來舉個例子通俗易懂講解非同步(二)ioslate迴圈機制

語言: CN / TW / HK

前言

我們在開發flutter應用的時候編寫程式碼,要麼是同步程式碼,要麼是非同步程式碼。那麼什麼是同步什麼是非同步呢?

  • 同步程式碼就是正常編寫的程式碼塊
  • 非同步程式碼就是Future,async等關鍵字修飾的程式碼塊

一、時機不同

他們區別於執行時機不同,同步程式碼先執行,非同步程式碼後執行,即使你的同步程式碼寫在最後,那也是你的同步程式碼執行,之後執行你的非同步程式碼。

二、機制不同

非同步程式碼執行在 event loop中,類似於Android裡的Looper機制,是一個死迴圈,event loop不斷的從事件佇列裡取事件然後執行。

event loop迴圈機制

如圖所示,事件存放於佇列中,loop迴圈執行 執行圖 Dart的事件迴圈如下圖所示。迴圈中有兩個佇列。一個是微任務佇列(MicroTask queue),一個是事件佇列(Event queue)。 在這裡插入圖片描述 事件佇列包含外部事件,例如I/O, Timer,繪製事件等等。 微任務佇列則包含有Dart內部的微任務,主要是通過scheduleMicrotask來排程。

  1. 首先處理所有微任務佇列裡的微任務。
  2. 處理完所有微任務以後。從事件佇列裡取1個事件進行處理。
  3. 回到微任務佇列繼續迴圈。

Dart要先把所有的微任務處理完,再處理一個事件,處理完之後再看看微任務佇列。如此迴圈。

例子:

```bash 8個微任務 2個事件

Dart-->執行完8個微任務 Dart-->執行完1個事件 Dart-->檢視微任務佇列 Dart-->再執行完1個事件 done ```

非同步執行

那麼在Dart中如何讓你的程式碼非同步執行呢?很簡單,把要非同步執行的程式碼放在微任務佇列或者事件佇列裡就行了。

可以呼叫scheduleMicrotask來讓程式碼以微任務的方式非同步執行

bash scheduleMicrotask((){ print('a microtask'); });

可以呼叫Timer.run來讓程式碼以Event的方式非同步執行

bash Timer.run((){ print('a event'); });

Future非同步執行

建立一個立刻在事件佇列裡執行的Future: bash Future(() => print('立刻在Event queue中執行的Future'));

建立一個延時1秒在事件佇列裡執行的Future:

bash Future.delayed(const Duration(seconds:1), () => print('1秒後在Event queue中執行的Future'));

建立一個在微任務佇列裡執行的Future:

bash Future.microtask(() => print('在Microtask queue裡執行的Future'));

建立一個同步執行的Future:

bash Future.sync(() => print('同步執行的Future'));

這裡要注意一下,這個同步執行指的是構造Future的時候傳入的函式是同步執行的,這個Future通過then串進來的回撥函式是排程到微任務佇列非同步執行的。

有了Future之後, 通過呼叫then來把回撥函式串起來,這樣就解決了"回撥地獄"的問題。

bash Future(()=> print('task')) .then((_)=> print('callback1')) .then((_)=> print('callback2'));

在task列印完畢以後,通過then串起來的回撥函式會按照連結的順序依次執行。 如果task執行出錯怎麼辦?你可以通過catchError來鏈上一個錯誤處理函式:

bash Future(()=> throw 'we have a problem') .then((_)=> print('callback1')) .then((_)=> print('callback2')) .catchError((error)=>print('$error'));

上面這個Future執行時直接丟擲一個異常,這個異常會被catchError捕捉到。類似於Java中的try/catch機制的catch程式碼塊。執行後只會執行catchError裡的程式碼。兩個then中的程式碼都不會被執行。

既然有了類似Java的try/catch,那麼Java中的finally也應該有吧。有的,那就是whenComplete:

```bash

Future(()=> throw 'we have a problem') .then(()=> print('callback1')) .then(()=> print('callback2')) .catchError((error)=>print('$error')) .whenComplete(()=> print('whenComplete')); ```

無論這個Future是正常執行完畢還是丟擲異常,whenComplete都一定會被執行。

結果執行

把如上的程式碼在dart中執行看看輸出

```bash print('1'); var fu1 = Future(() => print('立刻在Event queue中執行的Future')); Future future2 = new Future((){ print("future2 初始化任務"); }); print('2'); Future.delayed(const Duration(seconds:1), () => print('1秒後在Event queue中執行的Future')); print('3'); var fu2 = Future.microtask(() => print('在Microtask queue裡執行的Future')); print('4'); Future.sync(() => print('同步執行的Future')).then((value) => print('then同步執行的Future')); print('5'); fu1.then((value) => print('then 立刻在Event queue中執行的Future')); print('6'); fu2.then((value) => print('then 在Microtask queue裡執行的Future')); print('7'); Future(()=> throw 'we have a problem') .then(()=> print('callback1')) .then(()=> print('callback2')) .catchError((error)=>print('$error')); print('8'); Future(()=> throw 'we have a problem') .then(()=> print('callback1')) .then(()=> print('callback2')) .catchError((error)=>print('$error')) .whenComplete(()=> print('whenComplete')); print('9'); Future future4 = Future.value("立即執行").then((value){ print("future4 執行then"); }).whenComplete((){ print("future4 執行whenComplete"); }); print('10');

            future2.then((_) {
              print("future2 執行then");
              future4.then((_){
                print("future4 執行then2");
              });

            });

```

輸出

```bash I/flutter (29040): 1 I/flutter (29040): 2 I/flutter (29040): 3 I/flutter (29040): 4 I/flutter (29040): 同步執行的Future I/flutter (29040): 5 I/flutter (29040): 6 I/flutter (29040): 7 I/flutter (29040): 8 I/flutter (29040): 9 I/flutter (29040): 10 I/flutter (29040): 在Microtask queue裡執行的Future I/flutter (29040): then 在Microtask queue裡執行的Future I/flutter (29040): then同步執行的Future I/flutter (29040): future4 執行then I/flutter (29040): future4 執行whenComplete I/flutter (29040): 立刻在Event queue中執行的Future I/flutter (29040): then 立刻在Event queue中執行的Future I/flutter (29040): future2 初始化任務 I/flutter (29040): future2 執行then I/flutter (29040): future4 執行then2 I/flutter (29040): we have a problem I/flutter (29040): we have a problem I/flutter (29040): whenComplete I/flutter (29040): 1秒後在Event queue中執行的Future

```

輸出說明: - 先輸出同步程式碼,再輸出非同步程式碼 - 通過then串聯起的任務會在主要任務執行完立即執行 - Future.sync是同步執行,then執行在微任務佇列中 - 通過Future.value()函式建立的任務是立即執行的 - 如果是在whenComplete之後註冊的then,那麼這個then的任務將放在microtask執行

Completer

Completer允許你做某個非同步事情的時候,呼叫c.complete(value)方法來傳入最後要返回的值。最後通過c.future的返回值來得到結果,(注意:宣告完成的complete和completeError方法只能呼叫一次,不然會報錯)。 例子:

```bash test() async { Completer c = new Completer(); for (var i = 0; i < 1000; i++) { if (i == 900 && c.isCompleted == false) { c.completeError('error in $i'); } if (i == 800 && c.isCompleted == false) { c.complete('complete in $i'); } }

try {
  String res = await c.future;
  print(res); //得到complete傳入的返回值 'complete in 800'
} catch (e) {
  print(e);//捕獲completeError返回的錯誤
}

} ```