首頁 > 軟體

Dart多個future佇列完成加入順序關係及原子性論證

2022-11-13 14:01:02

引言

Dart 是一個在單執行緒中執行的程式。在程式中執行一個需要長時間的執行的操作,為避免卡住UI主執行緒,我們會用到非同步(future),可以使程式在等待一個耗時操作完成時繼續處理其他工作。

在進入正題之前,我們先了解一下 Dart 的訊息迴圈機制:

  • Dart 從兩個佇列執行任務:Event事件佇列Microtask微任務佇列
  • 事件迴圈會優先處理微任務佇列,microtask清空之後才將 event事件佇列中的下一個專案出隊並處理
  • 事件佇列具有來自Dart(Future,Timer,Isolate Message等)和系統(使用者輸入,I/O等)
  • 微任務佇列目前僅包含來自Dart,當然我們也可以自己往微佇列中插入任務

什麼是 Future

Future 是什麼?這裡我們用小篇幅簡單描述一下。future是一個非同步的執行操作,可以在不阻塞程式碼的情況下實現耗時功能。

main() {
  Future(() {
    print('我是一個耗時操作');
  }).then((value){
    print('future 結束了');
  });
  print('main');
}

 //列印結果
main
我是一個耗時操作
future 結束了

在專案中,我們使用 FutureAsyncawait 相互組合實現非同步編碼。

Future 操作具備'原子性'嗎

上面的篇幅我們說過,future 建立後會被直接加入到事件佇列依次執行。那麼在上一個 future 在沒有標識完成前,下一個 future 可以被執行嗎?

小編寫了幾個樣例來做實驗:

實驗寫法一:

main() {
  Test1.future1().then((value) => print(value));
  Test1.future2().then((value) => print(value));
}
abstract class Test1 {
  static Future<String> future1() async {
    return Future(() async {
      print('開始 future1');
      await TestTool.timeConsume(1000000000); //耗時運算
      print('一千年過去了');
      return 'future1 結束了';
    });
  }
  static Future<String> future2() async {
    return Future(() async {
      print('開始 future2');
      await TestTool.timeConsume(1000); //耗時運算
      print('繼續 future2');
      return 'future2 結束了';
    });
  }
}

 //列印結果
開始 future1
一千年過去了
future1 結束了
開始 future2
繼續 future2
future2 結束了

實驗結果:

  • 從列印結果上看,future任務沒有中斷,執行完當前任務後才可執行佇列中的下一個future

實驗寫法二:

main() {
  Test2.future1().then((value) => print(value));
  Test2.future2().then((value) => print(value));
}
abstract class Test2 {
  static Future<String> future1() async {
    print('開始 future1');
    await TestTool.timeConsume(1000000000);//耗時運算
    print('一千年過去了');
    return 'future1 結束了';
  }
  static Future<String> future2() async {
    print('開始 future2');
    await TestTool.timeConsume(1000);//耗時運算
    print('繼續 future2');
    return 'future2 結束了';
  }
}

//列印結果
開始 future1
開始 future2
一千年過去了
future1 結束了
繼續 future2
future2 結束了

實驗結果:

  • future2future1沒有結束前就已經開始了任務。
  • future2會在future1任務執行完成後響應結束,整個過程仍然保持了完成順序與加入事件佇列的順序一致性。

實驗寫法三:

main() {
  Test3.future1().then((value) => print(value));
  Test3.future2().then((value) => print(value));
}
abstract class Test3 {
  static Future<String> future1() async {
    print('開始 future1');
    await Future(() => TestTool.timeConsume(1000000000));//耗時運算
    print('一千年過去了');
    return 'future1 結束了';
  }
  static Future<String> future2() async {
    print('開始 future2');
    await TestTool.timeConsume(1000);//耗時運算
    print('繼續 future2');
    return 'future2 結束了';
  }
}

//列印結果
開始 future1
開始 future2
繼續 future2
future2 結束了
一千年過去了
future1 結束了

實驗結果:

  • 從列印結果上看,future1開始後,future2直接開始任務,且future2任務完成後直接標識完成。
  • future1future2 的完成順序已經和加入事件佇列的順序無關了,只與內部耗時正相關。

附上耗時程式碼:

abstract class TestTool {
  ///耗時操作
  static Future<int> timeConsume(int num) async {
    final result = _timeConsume(num);
    return result;
  }
  static int _timeConsume(int num) {
    int count = 0;
    while (num > 0) {
      if (num % 2 == 0) {
        count++;
      }
      num--;
    }
    return count;
  }
}

論證結論

綜合上述三種寫法分析:

future方法體內部不屬於可靠的'原子性操作',不同的寫法有不同的差異性。 如果想將整個方法體內部作為不可拆分的執行單位。在外層使用Future進行包裹處理,如寫法一中Test1範例:

static Future<T> funcName() async {
  return Future(() async {
    ...
    具體的方法體內容
    ...
    return result;
  });
}

future在建立的同時,就會被加入到event事件佇列中。事件佇列是依次執行的,但每個future的完成順序與加入的順序不存在可靠的一致性。 如果在業務內想保持順序的一致性,可參考上述寫法,或使用 await 進行強制等待如:

main() async {
  await Test2.future1().then((value) => print(value));
  Test2.future2().then((value) => print(value));
}

這樣寫,future2 就一定會在 future1 執行完成後才進入開始狀態。

以上就是Dart多個future佇列完成加入順序關係及原子性論證的詳細內容,更多關於Dart多個future原子性的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com