KabologHomeXGitHub

Dartの非同期処理の理解を深める(キュー編)

Dart Advent Calendar 2022 の 18 日目の記事です。

記事を書くのに良い時期なので、PC 上にあったメモを記事にすることにしました。
キュー編では次のあたりがわかるようになる情報をまとめています。

  • 時々出てくる microtask って何のこと?
  • FutureFuture.valueFuture.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.microtaskscheduleMicrotask() などで 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 に入ります。

参考資料

Xに投稿する