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;
}