Dart-Async/Awaitをちょっと調査にちょっと近い感じだけど、非同期対応(Future/マイクロタスク)とその実行順序についてしらべてみた。
非同期対応
Dartではasynchronyサポートと呼んでいて、これはFuture/Streamが該当する。
上記にページに書かれてはいないのだけど、マイクロタスクもこの非同期対応の一部に含まれているのではないかと考えている。
マイクロタスクとはZone.scheduleMicrotaskで登録される関数。
DartはFuture/Stream及びマイクロタスクをイベントループ的な処理で実行していて、awaitで登録されている処理の切り替えを行っている。
過去の記事になるけど、それに関しての実装概要みたいなものが記述されているのがインターネットアーカイブに残っている。
切り替えに関しても優先順位が存在し、基本的にマイクロタスク、Future/Streamの順になっている。
非同期処理の実行順序
処理の実行順序を調べるため、とりあえず非同期対応を網羅しているプログラムを作ってみた。
元ネタは上のevent-loopページのQuestion #2のソース。
これを実行すると処理の流れが見えてくる。
- awaitかmain関数から抜けるまで、基本呼び出し順序で処理される。
0000000 main #1 of 20000000 start0001000 func #00001300 main #2 of 2
- この部分で注目が「func #0」。
これはfunc1()関数の中のprint文になるのだけど、この関数asyncでFutureを返す感じにしているのだけど、他の非同期処理に切り替わらず順序を守り処理されている。
- この部分で注目が「func #0」。
- mainが抜けた時点でFuture及びマイクロタスク処理に移るのだけど、まず初めはマイクロタスクが実行される。
0001400 microtask #1 of 30001400 f.micro #10001500 f.micro sync0002100 f.micro #1 end 10002199 microtask #2 of 30002199 microtask #3 of 3
- microtask #2及び#3はfuture #2より後に登録されているのだけど、優先順位が高いのでfuture #2より前に処理されている。
- f.microはFuture.microtaskで呼び出しており、一見Futureな感じだけど、中身はscheduleMicrotaskを使っているのでマイクロタスクと同じ優先順位になる。
- f.micro syncはf.micro内で順序立てて処理されているのだけど、これはFuture.syncを使っているためで、Future.syncは指定された登録関数をまず呼び出す実装になっている。
実行順序としてはfunc1()を呼び出したのと同じで、リターン値だけFutureでラッピングされている感じになる。
- mainに直書きされているFutureの実行
0002699 future #2 of 40002699 future #2a0002900 future #2b0002900 future #2c0002900 microtask #0 (from future #2b)0003000 func #0 future #10003199 func #0 future #20003199 future #3 of 40003300 future #4 of 4
- future #1はdelayedで遅延をかまされているので、今回の実行タイミングからは外されている。
- func #0 future #1と#2はfunc1()内で登録されたFutureなのでmain側で登録されたFutureと同じタイミングでの実行になり、またその実行順序は登録順になっている。
- microtask #0はfuture #2内で登録されたマイクロタスクなのでfuture #2が完了した時点でスケジュール、実行されている。
- Future内で登録されたFutureの実行
0005199 future #3a (a new future)0005300 future #3b
- future #3aはFuture #3内で新規に追加されたものなので、ひとつ前のタイミングより後に追加スケジュールされた感じになるので、ここで実行される。
- 遅延設定されたFutureの実行
実行順序で気を付けなければいけないのは、Future.microtask、Future.syncになるのか。
Future.microtaskはFuture<T>を返したいのだけどscheduleMicrotaskの優先順位にしたいもの。
Future.syncは処理は即実行したいけどFuture<T>をリターンしたいという要求でできたものなのだろうか。
Future.syncは普通にFuture<T> func() {} みたいな関数を作って呼び出せばいいよう気がするのだけど。
Future.delayedの実際の遅延時間
サンプルプログラムでは、左に出てくる数値は、Stopwatchを使った経過マイクロ秒を表示している。
future #1のDurationの中身を変更することで、設定した遅延時間と実際の遅延時間の差を見て取ることができる。
今回の例では2.4msほどの差が出ている。
Durationでの遅延に関しては、シビアになると困る感じなのだろうか。
コメント