Qtブログ(日本語)

Qt 6.11 の新機能:Qt Task Tree モジュールのご紹介

作成者: Qt Group 日本オフィス|Dec 23, 2025 7:48:26 AM
このブログは「Introducing the Qt Task Tree Module, Part of Qt 6.11の抄訳です。
 

Qt 6.11 で Technology Preview としてまもなく利用可能になる Qt Task Tree モジュールは、自動化された非同期タスクを管理するための包括的なソリューションです。しかし、このモジュールの本当の特長は、単なる機能追加にとどまりません。最大のポイントは、Qt における C++ API 設計に対する、まったく新しいアプローチを採用している点です。これにより、非同期コードを書くときの考え方そのものが変わり、同時に、コードを「読む」体験も大きく改善されます。さらに Qt Task Tree は、これまでバラバラに存在していたさまざまな非同期 API を統一的に扱える仕組みを提供します。加えて、既存の非同期タスクをこのモジュールに適応させるための手段も用意されており、柔軟に組み込むことが可能です。

Task Tree Demo デモアプリ(スクリーンショット)

Qt C++ API における、まったく新しい宣言的アプローチ

Qt は、QML 言語による宣言的 API でよく知られています。新しく登場する Qt Task Tree モジュールは、この宣言的な API の考え方を Qt の C++ の世界にもたらすものです。

宣言的な「レシピ」とタスク・ツリー

非同期ワークフローの宣言的な記述は、「レシピ(recipe)」と呼ばれる仕組みで表現されます。このレシピは コピー可能な値型オブジェクトで、タスクツリーのインスタンスに渡されます。タスクツリーが開始されると、渡されたレシピを読み取り、その内容に基づいて 複雑な非同期ワークフローを自動的に管理します。このワークフローには、以下のような処理が含まれます。

  • 非同期タスクの動的な生成
  • タスク間でデータをやり取りするためのデータオブジェクトの動的生成
  • 生成されたタスクのライフサイクル管理
  • タスク完了後に実行される継続処理(コンティニュエーション)の実行

レシピは 再利用可能であり、より汎用的なレシピの一部として組み込むこともできます。また、同じレシピを複数回実行したり、複数のタスクツリーが並列に同じレシピを実行したりすることも可能です。

カートリッジとプレイヤーのたとえ

レシピとタスクツリーの関係は、ゲームカートリッジとカートリッジプレイヤーの関係にたとえることができます。カートリッジそのものは、単体では何もしません。同じように、レシピを定義している段階では、タスクやデータオブジェクトは一切生成されません。行っているのは、「このレシピがタスクツリーに渡され、タスクツリーが開始されたときに、何をどう実行すべきか」という処理内容の記述だけです。これは、カートリッジをプレイヤーに差し込み、再生を開始したときとまったく同じです。プレイヤーはカートリッジの中身を読み取り、「何をすればよいか」を正確に理解した上で処理を実行します。

新しいゲームを作りたいときは、新しいカートリッジを設計するだけで済みます。プレイヤー側を変更する必要はありません。カートリッジを差し込み、Play / Cancel / Reset ボタンを操作するだけです。レシピも同様です。自分独自の複雑な非同期ワークフローを書きたい場合は、レシピを用意するだけでOKです。そのレシピをタスクツリーに渡し、タスクツリーを再生/キャンセル/リセットするだけで実行できます。レシピを正しく実行するために、タスクツリー自体を変更する必要はありません。

このように、「コードを書く」という考え方そのものを切り替えることが、このモジュールを効果的に使いこなすための重要なポイントになります。

役割分担について

Qt 初の宣言的 C++ API であるため、このモジュールをどのように使うのか、そして レシピ と タスクツリー がそれぞれどこまでを責任範囲とするのかを正しく理解することが重要です。

Qt Task Tree モジュールの利用者が注力すべきなのは、目的とする非同期ワークフローを正確に記述したレシピを作ることです。レシピの内容は完全に宣言的であり、あくまで「こうしたい」という処理の説明書にすぎない、という点を常に意識する必要があります。

もう一つ重要なのは、タスクツリー自身がレシピの読み手であり、実行者であるという点です。
タスクツリーの使い方はとてもシンプルで、QTaskTree をサブクラス化する必要はありません(むしろ、すべきではありません)。レシピを渡して実行するだけです。あとは、キャンセルやリセットを行ったり、完了通知を受け取ったりするだけ。それで十分です。そして、ここが最大のメリットです。
タスクツリーは内部で、大量の退屈で定型的な処理(ボイラープレートコード)を自動的に肩代わりしてくれます。しかも、追加のコードを書く必要は一切ありません。

それでは、レシピとタスクツリーがそれぞれ何を担当し、レシピの内容が実行中のタスクツリーにどのような影響を与えるのかを、具体的に見ていきましょう:

Recipe (カートリッジ), ユーザーによって定義され、次の内容を記述:

QTaskTree (プレイヤー), ユーザーによってインスタンス化され、
渡されたレシピを読み取り、以下の処理を自動的に実行:

どのタスクを実行するのか タスクの生成と破棄を行い、それらのライフサイクルを管理する
どの順序で実行するか レシピで定義されたタスク実行順を忠実に守る
実行モード(逐次/並列) タスクを逐次、または並列で実行する
ワークフローポリシー 完了したタスクの結果と、定義されたワークフローポリシーに基づいて実行を制御する。具体的には、後続処理(continuation)を実行したり、残りのタスクをスキップしたり、並列実行中のタスクをキャンセルしたりする
ネストされたグループ 複数のサブタスクからなるグループを実行する。このグループは、親タスクから見ると 1つの非同期タスク として扱われ、独自の実行モード(逐次/並列)やワークフローポリシーを持つ
セットアップハンドラ タスクやグループの開始前にセットアップハンドラを実行する。
これにより、ユーザーの要件に応じてタスクを事前に設定・構成することが可能になる。
完了ハンドラ(Done ハンドラ) タスクやグループの完了時に、Done ハンドラを実行する。
これにより、完了したタスクから結果データを収集することが可能になる。
タスク間データ共有のための Storage<DataType> オブジェクト 対応する Storage<DataType> の定義に基づいて、DataType オブジェクトを動的に生成・破棄する。これらのオブジェクトは、異なるタスクやグループのセットアップハンドラや完了ハンドラ、あるいは条件式の中で使用され、タスク間のデータ受け渡しを可能にする。
条件式(Conditional expressions) 収集されたデータや、非同期タスクの実行結果に基づいて、実行フローを分岐させ、異なる処理パスを選択する。

さらに QTaskTree は、完了したタスクに関する基本的な進捗情報を提供するとともに、実行状態に関係なく、現在動作中のすべてのタスクをまとめてキャンセルできる仕組みを備えています。
開発者の視点から見ると、やるべきことは レシピ(表の左列)を定義することだけ です。表の右列を見ると分かるように、QTaskTree がどれだけ多くの処理を追加コードなしで肩代わりしてくれているか が一目瞭然です。この仕組みにより、非同期プログラミングで通常必要とされる、ほぼすべてのボイラープレートコードが不要になります。

宣言的アプローチの力

ワークフローそのものとボイラープレートコードを明確に分離することで、従来の手法に比べてコードの可読性は大きく向上します。面倒で冗長になりがちなボイラープレートコードはタスクツリーが引き受けてくれるため、開発者のコードからは自然と姿を消します。一方で、実際のワークフローは、たとえ複雑であっても、ひとつの場所に明確かつ正確に記述されます。処理の流れを追うためにコードを行き来する必要はありません。

それでは、シンプルなレシピの例がどのように書けるのか、見ていきましょう。

このレシピは、最上位に Group 要素を持つ構造になっています。この Group の中で、プロパティやタスクを定義していきます。以下の説明では 「このレシピが実行されたとき(when this recipe is run)」 という表現をあえて使っています。これは、処理が起きるのはレシピを定義した瞬間ではなく、後から実行されたタイミングであるという点を強調するためです。つまり、レシピはあくまで「何をどう実行するか」を記述した設計図であり、実際の処理は、そのレシピがタスクツリーに渡され、実行されたときに初めて行われます。

最初のプロパティの値は sequential です。これは、このレシピが実行されたとき、最上位グループの直下にあるタスクが 順番に連続して実行されることを意味します。つまり、あるタスクが完了してから、次のタスクが開始されます。この sequential は、すべての Group における デフォルトの実行モードであるため、明示的に指定しなくても同じ動作になります。そのため、省略することも可能です。

2つ目のプロパティの値は stopOnError です。これは、このレシピが実行された際に、最上位グループ直下のいずれかのタスクがエラーで終了した場合、その時点でグループ全体の実行を停止し、まだ実行されていないタスクはすべてスキップされ、エラーとして結果が報告されることを意味します。
一方、すべてのタスクが正常に完了した場合は、成功として実行結果が報告されます。この stopOnError もまた、すべての Group における デフォルトのワークフローポリシーであるため、こちらも省略可能です。

次に、最上位グループの最初のタスクとして QNetworkReplyWrapperTask を定義します。このタスクは、このレシピが実行されたときに、ネットワークから非同期でデータを取得します。

最上位グループの最後の要素は、ネストされた Group です。このグループは、ダウンロードタスクが正常に完了した後に実行されます。グループには parallel プロパティが設定されており、内部のすべてのタスクが並列に実行されることが保証されています。このグループ内では、2つの QConcurrentCallTask<QImage> タスクを定義しています。それぞれのタスクは、このレシピが実行されたときに別々のスレッドで関数を実行し、取得したネットワークデータを元に、指定されたサイズの QImage に変換します。

最後に、このレシピを QTaskTree のインスタンスに渡して実行します。これで、一連の非同期ワークフローがレシピどおりに開始されます。

さまざまな非同期 API の統合

Qt Task Tree モジュールは、非同期プログラミングにおける非常に重要な課題のひとつに対応しています。それが、複数の非同期 API を、単一で一貫性のあるインターフェースに統合することです。

このような統合がなければ、種類の異なるタスクを自動的に生成し、開始し、完了を通知し、そして適切に破棄する、といった一連の処理を統一的に扱うのは、非常に困難になります。
以下の表は、Qt に存在するさまざまな非同期 API が、それぞれどのようにタスクを開始するのかを示したものです。タスクの生成・終了・破棄についても、同様に API ごとの違いが存在します。

Asychronous Task

Starting API

Process QProcess::start()
Network Query QNetworkAccessManager::get()/put()/post()/other…
Instantiates QNetworkReply dynamically.
Concurrent Call QtConcurrent::run()
Returns QFuture handle
Timer QTimer::singleShot()

 

Qt Task Tree モジュールでは、これまで課題となっていた非同期 API 同士の非互換性の問題が解消されています。既存の各種非同期 API に対して用意されたタスクアダプタを通じて、これらすべての型が共通のインターフェースに統合されているためです。これにより、異なる種類の非同期処理であっても、Qt Task Tree の仕組みの中で一貫した形で扱うことが可能になります。
組み込みで提供されているアダプタの詳細や、独自の非同期タスク型を Qt Task Tree に対応させる方法については、QTaskInterface および QCustomTask のドキュメントを参照してください。

Task Tree の実践例

Qt Task Tree モジュールは、もともと 2022 年後半に QtCreator プロジェクトの一部として開発されました。その後の開発を通じて、多くの機能強化が加えられてきました。ここでは、その中から代表的な改善点をいくつか紹介します。

  • For ループによるレシピの部分実行
  • 非同期タスクに直接作用する条件式: If () >> Then {} >> Else {}
  • 短絡評価(ショートサーキット)で動作する、非同期タスク向けの論理演算子 AND, OR, NOT
  • レシピを単体(single)/順次(sequential)/並列(parallel)/マップ(mapped)実行できる、使いやすいタスクツリーランナー(task tree runners )

Qt Task Tree は最終的に十分成熟し、Qt 6.11 にて パブリック API として公開されることになりました。現在 Qt Task Tree は、QtCreator の中で 100か所以上で実際に使われています。具体的には、

  • プロジェクトの ビルド/デプロイ/実行設定の管理
  • すべての ロケーターフィルタの実行制御
  • VCS(バージョン管理システム)コマンドの実行
  • Axivion プラグインのネットワーク通信制御
  • Clang ツールの実行
  • 自動テスト(autotest)の実行

など、Qt Creator の中核となる多くの処理を担っています。さらに、これ以外にもさまざまな用途で活用されています。

Qt Task Tree モジュールには、その機能を実際に確認できる いくつかのサンプルも用意されています。次に、それらを簡単に見ていきましょう。

実行モードとワークフローポリシーを試してみる

Demo サンプルでは、異なる実行モードやワークフローポリシーが、複雑なタスクツリー構造の中でサブタスクの実行にどのような影響を与えるかを確認できます。
また、各タスクの実行時間や想定される実行結果が、その後の処理の継続にどのように影響するのかを観察することも可能です。

このサンプルは web assembly 版 も用意されているため、ブラウザ上で直接操作しながら挙動を試すことができます。

ステートマシンとしての活用例

Traffic Light(信号機)サンプルでは、タスクツリーをステートマシンとして利用する方法を示しています。この例では Forever 要素を使って、繰り返し実行されるレシピを構築し、それをステートマシンとして機能させる方法を解説しています。Forever を用いることで、状態遷移を伴う処理を宣言的に表現でき、タスクツリーを使った状態管理の可能性を具体的に理解できます。

イテレーションの並列実行

Qt Task Tree では、複数の非同期イテレーションを同時に開始することが可能です。この仕組みは Image Scaling(画像スケーリング) のサンプルで紹介されています。この例では、複数の処理を並列に実行する方法に加えて、Storage オブジェクトを使って、異なるタスク間で動的なデータをやり取りする方法についても解説しています。これにより、並列処理を行いながらも、タスク間で安全かつ柔軟にデータ共有を行えることが分かります。

追加リンク

以下のリンクから、完全なドキュメントソースコードツリー、そして追加情報をまとめた Task Tree の総合ページ をご覧いただけます。

あなたのご意見をお聞かせください!

Qt 6.11 の公式パブリックリリースに、この新しいモジュールを含められることを、私たちは本当に嬉しく思っています。Qt における 初の宣言的 C++ API が登場したことは、非常にエキサイティングな出来事です。この革新的な API 設計によって、Qt ユーザーの皆さんが「コードを書く」「コードを読む」という考え方をどのように変えていくのか、私たち自身もとても興味を持っています。
ぜひ皆さんのフィードバックをお聞かせください。それでは、新しい Qt Task Tree モジュール を存分に楽しんでください!