05. FlutterによるDartの非同期開発(2)

3. Dartの非同期補足

3.1. タスクの実行順序

3.1.1. Microtask キューについて

以前の調査では、コードを実行するためのイベント ループ (イベント ループ) が Dart にあり、その中にイベント キュー (イベント キュー) があり、イベント ループは実行のためにイベント キューからイベントを継続的にフェッチすることがわかっています。 .
しかし、厳密に分けると、Dart には別のキューがあります: Microtask キューです。

  • マイクロタスク キューの優先度は、イベント キューの優先度よりも高くなります。
  • つまり、イベントループは必ずマイクロタスクキューのタスクを先に実行してからイベントキューのタスクを実行するので、
    Flutterの開発では、どれをイベントキューに入れ、どれをイベントキューに入れるか、マイクロタスクキュー?
  • IO、タイマー、クリック、描画イベントなど、すべての外部イベント タスクはイベント キューにあります。
  • 通常、マイクロタスクは Dart 内から発生し、マイクロタスクはほとんどありません。これは、マイクロタスクが多すぎると、イベント キューがキューに入れられず、タスク キューの実行がブロックされるためです (たとえば、ユーザーがクリックしても応答がない場合)。

そうは言っても、少し面倒だったかもしれません. Dart のシングル スレッドでは、コードはどのように実行されますか?

  • 1. Dart のエントリ ポイントはメイン関数であるため、メイン関数のコードが最初に実行されます。
  • 2. main 関数の実行後、イベント ループ (Event Loop) が開始され、キュー内のタスクが起動後に実行されます。
  • 3. まず、microtask キュー (Microtask Queue) 内のすべてのタスクが先入れ先出しの順序で実行されます。
  • 4. 次に、イベント キュー (Event Queue) 内のすべてのタスクが先入れ先出しの順序で実行されます。

ここに画像の説明を挿入

3.1.2. マイクロタスクの作成方法

開発中は、dart の async で scheduleMicrotask を使用してマイクロタスクを作成できます。

import "dart:async";

main(List<String> args) {
    
    
  scheduleMicrotask(() {
    
    
    print("Hello Microtask");
  });
}

開発中に、イベント キューに入れておきたくないタスクがある場合は、マイクロタスクを作成できます。

Future コードはイベント キューまたはマイクロタスク キューに追加されますか?

通常、Future には 2 つの関数実行本体があります。

  • Future コンストラクターによって渡された関数本体
  • thenの関数本体(catchErrorも同等扱い)

では、それらはどのキューに追加されるのでしょうか?

  • Future コンストラクターによって渡された関数本体は、イベント キューに配置されます。
  • then の関数本体は 3 つのケースに分ける必要があります:
    ケース 1: Future が実行されない (実行するタスクがある) 場合、then は Future の関数本体に直接追加されます
    。 then は Future が実行された後に実行され、then の関数本体はマイクロタスク キューに配置され、マイクロタスク キューは現在の Future が実行された後に実行されます; ケース 3: Future がチェーン コールの場合、それは
    、 then は実行されず、次の then も実行されません。
// future_1加入到eventqueue中,紧随其后then_1被加入到eventqueue中
Future(() => print("future_1")).then((_) => print("then_1"));

// Future没有函数执行体,then_2被加入到microtaskqueue中
Future(() => null).then((_) => print("then_2"));

// future_3、then_3_a、then_3_b依次加入到eventqueue中
Future(() => print("future_3")).then((_) => print("then_3_a")).then((_) => print("then_3_b"));

3.1.3. コードの実行順序

前のルールに従って、最終的なコード実行シーケンスのケースを学びましょう。

import "dart:async";

main(List<String> args) {
    
    
  print("main start");

  Future(() => print("task1"));
	
  final future = Future(() => null);

  Future(() => print("task2")).then((_) {
    
    
    print("task3");
    scheduleMicrotask(() => print('task4'));
  }).then((_) => print("task5"));

  future.then((_) => print("task6"));
  scheduleMicrotask(() => print('task7'));

  Future(() => print('task8'))
    .then((_) => Future(() => print('task9')))
    .then((_) => print('task10'));

  print("main end");
}

コード実行の結果は次のとおりです。


main start
main end
task7
task1
task6
task2
task3
task5
task4
task8
task9
task10

コード分​​析:

  • 1. main 関数が最初に実行されるため、main start と main end が最初に実行されますが、問題はありません。
  • 2. メイン関数の実行中に、いくつかのタスクが EventQueue と MicrotaskQueue にそれぞれ追加されます。
  • 3. task7 は scheduleMicrotask 関数によって呼び出されるため、最初に MicrotaskQueue に追加され、最初に実行されます。
  • 4. 次に、EventQueue の実行を開始します。タスク 1 が EventQueue に追加されて実行されます。
  • 5. final future = Future(() => null); によって作成された Future の then がマイクロタスクに追加され、最初にマイクロタスクが直接実行されるため、task6 が実行されます。
  • 6. task2、task3、および task5 を一度 EventQueue に追加して実行します。
  • 7. task3 の印刷が実行された後、scheduleMicrotask が呼び出され、この EventQueue の実行後に実行されるため、task4 は task5 の後に実行されます (注: scheduleMicrotask の呼び出しは task3 のコードの一部であるため、task4 は必ず実行する必要があります)。 task5 実装後)
  • 8. Task8、task9、および task10 は一度 EventQueue に追加されて実行されます;
    実際、上記のコード実行シーケンスはインタビューに表示される場合があり、この種の複雑なネストは通常​​、開発では表示されず、十分に理解する必要があります。実行順序;
    ただし、上記のコードの実行順序を理解すると、EventQueue と microtaskQueue の理解が深まります。

3.2. マルチコアCPUの活用

3.2.1. Isolate の理解

DartにはIsolateという概念がありますが、それは何ですか?

  • Dart がシングル スレッドであることは既にわかっていますが、このスレッドには、アクセス可能な独自のメモリ空間と、実行する必要があるイベント ループがあります。
  • この宇宙システムをアイソレートと呼ぶことができます。
  • たとえば、Flutter には Root Isolate があり、UI レンダリング、ユーザー インタラクションなどの Flutter コードの実行を担当します。

Isolate では、リソースの分離が非常にうまく行われ、各 Isolate には独自のイベント ループとキューがあり、

  • Isolate はリソースを共有せず、メッセージ メカニズムにのみ依存して通信できるため、リソースのプリエンプションの問題はありません。
    ただし、Isolate が 1 つしかない場合は、永遠に 1 つのスレッドしか使用できないことを意味し、マルチコア CPU のリソースの無駄遣いになります。

開発中に多くの時間のかかる計算がある場合は、自分で Isolate を作成し、独立した Isolate で目的の計算操作を完了することができます。

アイソレートを作成するには?
Isolate の作成は比較的簡単で、Isolate.spawn を使用して作成できます。

import "dart:isolate";

main(List<String> args) {
    
    
  Isolate.spawn(foo, "Hello Isolate");
}

void foo(info) {
    
    
  print("新的isolate:$info");
}

3.2.2. 通信メカニズムの分離

しかし、実際の開発では、実行結果を気にせずに新しい Isolate を開くことはありません。

  • 計算には新しい Isolate が必要であり、計算結果を Main Isolate (つまり、デフォルトで有効になっている Isolate) に通知します。
  • Isolate は、パイプライン (SendPort) を送信することによってメッセージ通信メカニズムを実現します。
  • 並行 Isolate を開始するときに、メイン Isolate の送信パイプラインをパラメーターとして渡すことができます。
  • 同時実行が実行されると、このパイプラインを使用してメッセージを Main Isolate に送信できます。
import "dart:isolate";

main(List<String> args) async {
    
    
  // 1.创建管道
  ReceivePort receivePort= ReceivePort();

  // 2.创建新的Isolate
  Isolate isolate = await Isolate.spawn<SendPort>(foo, receivePort.sendPort);

  // 3.监听管道消息
  receivePort.listen((data) {
    
    
    print('Data:$data');
    // 不再使用时,我们会关闭管道
    receivePort.close();
    // 需要将isolate杀死
    isolate?.kill(priority: Isolate.immediate);
  });
}

void foo(SendPort sendPort) {
    
    
  sendPort.send("Hello World");
}

しかし、上記のコミュニケーションは一方向のコミュニケーションになりました.双方向のコミュニケーションが必要な場合はどうなりますか?

  • 実際、双方向通信のコードはもっと面倒です。
  • Flutter は、Isolate の作成と双方向通信を内部的にカプセル化する、同時計算をサポートする計算機能を提供します。
  • これを使用すると、マルチコア CPU をフルに活用でき、非常に簡単に使用できます。
    注: 次のコードは dart の API ではなく、Flutter の API であるため、Flutter プロジェクトでのみ実行できます。
main(List<String> args) async {
    
    
  int result = await compute(powerNum, 5);
  print(result);
}

int powerNum(int num) {
    
    
  return num * num;
}

おすすめ

転載: blog.csdn.net/qq_25218777/article/details/116403564