Chromium Chronicle #1: タスク スケジューリングのベスト プラクティス

Chrome チームがご紹介する Chromium Chronicle は、ブラウザを開発する Chromium デベロッパーやデベロッパーを対象とした月刊シリーズです。

Chromium Chronicle は主に、Chrome を作成、構築、テストするための技術的な知識とベスト プラクティスを広めることに重点を置いています。コードの健全性、便利なツール、単体テスト、ユーザー補助など、Chromium デベロッパーにとって関連性が高く有用なトピックを今後も取り上げる予定です。各記事は Chrome エンジニアが作成、編集します。

この新しいシリーズを楽しみにしています。皆様もぜひご期待ください。準備が整ったら 以下に最初のエピソードをご紹介します。

タスク スケジューリングのベスト プラクティス

エピソード 1: Gabriel Charette(PQ、モントリオール、2019 年 4 月)
前のエピソード

インプロセスの非同期実行を必要とする Chrome コードは通常、タスクをシーケンスに送信します。シーケンスは Chrome で管理される「仮想スレッド」であり、独自のスレッドを作成する場合よりも推奨されます。オブジェクトは投稿先のシーケンスをどのように判断するのでしょうか。

すべきでないこと

以前のパラダイムでは、作成者から SequencedTaskRunner を受け取ります。

Foo::Foo(scoped_refptr backend_task_runner)
    : backend_task_runner_(std::move(backend_task_runner)) {}
すべきこと

推奨されるパラダイムは、独立した SequencedTaskRunner を作成することです。

Foo::Foo()
    : backend_task_runner_(
          base::CreateSequencedTaskRunnerWithTraits({
              base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {}

すべての情報がローカルであり、無関係なタスクとの相互依存関係のリスクがないため、読み取りと書き込みが簡単になります。

このパラダイムはテストに関しても優れています。テストでは、タスクランナーを手動で挿入する代わりに、制御されたタスク環境をインスタンス化して Foo のタスクを管理できます。

class FooTest : public testing::Test {
 public
  (...)
 protected:
  base::test::TaskEnvironment task_environment_;
  Foo foo_;
};

TaskEnvironment をフィクスチャの先頭にすることで、Foo の存続期間全体を通じてタスク環境が必然的に管理されるようになります。TaskEnvironment は、Foo の構築中のリクエストをキャプチャして SequencedTaskRunner を作成し、各 FooTest でそのタスクを管理します。

非同期実行の結果をテストするには、RunLoop::Run()+QuitClosure() パラダイムを使用します。

TEST_F(FooTest, TestAsyncWork) {
  RunLoop run_loop;
  foo_.BeginAsyncWork(run_loop.QuitClosure());
  run_loop.Run();
  EXPECT_TRUE(foo_.work_done());
}

これは、非同期ワークロードに TaskEnvironment の領域外のタスク(システム イベントなど)が含まれる場合に不安定となる Run untilIdle() よりも推奨されます。そのため、RunUntilIdle() は慎重に使用してください。

もっと詳しくお知りになりたい場合スレッドとタスクに関するドキュメントをご覧になるか、TaskEnvironment への移行にご参加ください。