Dart Advent Calendar 2022 の 18 日目の記事です。
記事を書くのに良い時期なので、PC 上にあったメモを記事にすることにしました。
キュー編では次のあたりがわかるようになる情報をまとめています。
- 時々出てくる microtask って何のこと?
Future
、Future.value
、Future.sync
は何が違うの?
Dart の非同期処理
Dart はシングルスレッドの言語で、自分で Isolate(アイソレット)を追加しなければ main thread(main isolate)のみとなります。
それでも、何らかの処理を予約してすぐに別の処理を進めたり、様々な処理を行いながらボタンタップ/文字入力などのイベントを受け付けたりすることができます。
シングルスレッドでそのような非同期処理が可能なのは、イベントループ で キュー をうまく捌くようになっているからです。
キューとイベントループ
キュー
処理すべきタスクやイベントはキューに入れられます。
キューとは FIFO(First In, First Out: 先入れ先出し)の仕組みで、順番待ちの列のことです。
人気店の行列が先頭にいる人から順に捌かれるように、Dart で非同期に行われるべき処理も列を成して先頭のほうから取り出されて処理されます。
イベントループ
キューから取り出して処理することの繰り返しがイベントループです。
次々とキューに新たに入ってきてもループしながら取り出してはどんどん捌いていきます。
しかし通常はうまく捌くものの、別スレッドではないので重い処理で余裕がなくなると main isolate そのものの動作に影響します。
例えば Flutter で UI のブロック(アニメーションが止まる等)が起こります。
それを避けるのに isolate が役立ちますが割愛します。
ここでお伝えしておきたいのは、isolate ごとにイベントループがあることだけです。
詳細は ドキュメント をお読みください。
二つのキュー
キューには二種類があります。
- Microtask queue
- Event queue
優先度の違いだと理解しておけば良いと思います。
microtask queue は event queue より先に処理されます。
もし microtask がたくさんあって詰まっていれば event queue は捌かれないままとなります。
Future.microtask
、scheduleMicrotask()
などで microtask として予約することができますが、特別な理由がある場合を除いて「microtask queue に入れるぞ」と考える必要はありません。
理由がなければ event queue に入れればいいということになります。
Future の各コンストラクタとキューの関係
- Future.sync
- microtask queue に入る
Future.sync(() => v)
は、() => v
を今すぐに実行しつつそこで起こるエラーを捕捉したい場合に使える
- Future.value
- microtask queue に入る
Future.value(v)
はFuture.sync(() => v)
と同等- ソースコードによれば確かに同等に見える
- でも次の二つをその順に実行しても後者のほうが先になった(誰か理由を教えて)
Future.microtask(() => Future.value(v))
Future.microtask(() => Future.sync(() => v))
- こちらはなぜか
Future.microtask(() => v)
と同程度に早く実行される
- こちらはなぜか
- Future.error
- microtask queue に入る
Future.value(v)
のエラー版と捉えることができる
- Future.microtask
- microtask queue に入る
Future.microtask(() => v)
はscheduleMicrotask(() => v)
と同等
- Future.delayed
- event queue に入る
Future.delayed(Duration.zero, () => v)
はTimer.run(() => v)
と同等
- Future
- event queue に入る
Future(() => v)
はFuture.delayed(Duration.zero, () => v)
と同等Future.value(Future(() => v))
はFuture(() => v)
が microtask queue に入り、順番が来ると() => v
が event queue に入る
then((v) => ...)
も場合によってキューが絡みます。
この (v) => ...
は、Future が終わったときにはキューに入らずにすぐに実行されますが、Future が既に完了していれば microtask queue に入ります。
参考資料
- The Event Loop and Dart | webdev.dartlang.org(アーカイブ)
- [SERIOUS] Future() vs Future.value() vs Future.sync() vs Future.microtask() : dartlang
- Future(computation) constructor slow in javascript (by 1000 times slower than Future.value()) · Issue #48104 · dart-lang/sdk
- Dart asynchronous programming: Isolates and event loops | by Kathy Walrath | Dart | Medium