初めに
Qt Concurrentの「Promiseモード」について紹介します。
※拙著Qt5/Qt6入門 C++編に、モードの名前だけで説明を入れられなかった為、簡単にですがこちらで説明します。
Qt6におけるQt Concurrentの2つのモード
「Promiseモード(promise mode)」は、Qt6から追加されたQt Concurrentの新機能で、スレッド側の処理の進行状況と結果を非同期的にQFutureに伝達する簡単な方法を提供します。今まで(Qt5まで)のConcurrent Runとして実装されていた機能を「Basicモード(basic mode)」と位置づけ、それとは異なるモードとしてPromiseモードが追加されました。
そこで、Basicモード(basic mode)とPromiseモード(promise mode)それぞれの実装方法について、Qt Concurrent runを例に見ていきます。
Basicモード(basic mode)のQt Concurrent run
Basicモード(basic mode)を軽くおさらいします。
private:
static void work();
Qt Concurrent呼び出し
QtConcurrent::run(work);
staticな関数を用意し、その関数をQtConcurrent::run()の引数に渡します。
すると、work()が別スレッドで実行されます。
QFuture future = QtConcurrent::run(work);
QtConcurrent::run()はQFutureを返します。QFutureはテンプレートクラスです。スレッド側の処理結果がQFutureに格納されます。QtConcurrent::run()の返り値であるQFutureを取得します。QFuture.result()で処理結果を取得できます。
QFutureWatcher<int> m_watcher; //クラス変数として定義
m_watcher.setFuture(QtConcurrent::run(work));
QFutureをQFutureWatcherに設定することにより、シグナル・スロットでスレッド側の動作状況を取得できます。
QFutureWatcherクラス(正確にはQFutureWatcherクラスの親クラスであるQFutureWatcherBaseクラス)に、スレッドの状況を取得できる関数が用意されています。
- bool isCanceled() const
- bool isFinished() const
- bool isRunning() const
- bool isStarted() const
他にもシグナル・スロットも用意されています。
Promiseモード
※Promiseモードを使用したサンプルは、GitHubに上げてあります。
PromiseモードはQPromiseクラスを使用した機能です。Promiseモードでは、QtConcurrent::run()の引数に渡す関数を
private:
static void work();
から
private:
static void work(QPromise<int> &promise);
のように、第1引数にQPromiseを渡すことでPromiseモードになります。
QPromiseクラスのドキュメントに記載されている例だけを見ると、あたかもQt Concurrent起動側でQPromiseを生成し、それをQt Concurrent側に必ず渡さなければならないように見えますが、そんなことはありません。
Concurrent Run With Promiseの例に記載されているように、Qt Concurrent起動側でQPromiseを生成せずに、ただQtConcurrent::run()の引数に渡す関数の第1引数に、QPromiseを追加するだけでもPromiseモードとして実行できます。
※QPromiseを生成して渡す実装方法については、QPromiseクラスのスニペットを参考にしてください(本記事では省略します)。
Promiseサンプルの説明
私が作成したサンプルでは、Qt ConcurrentのSuspend/Pauseの動作を確認することができるサンプルとなっています。
Stopボタンが押された場合は完全に終了し、最初から処理をやり直します。Suspendボタンが押された場合は処理を中断し、Resumeボタンを押下すると、中断されたところから再開します。
サンプルコードの実装内容について見ていきます。
void Widget::work(QPromise<int> &promise)
{
for (int i = 0; i <= 100; i = i + 10) {
QThread::sleep(1);
promise.setProgressValue(i); //【1】
promise.suspendIfRequested(); //【2】
if (promise.isCanceled()) { //【3】
qDebug() << "isCanceled() is true.";
return;
}
}
}
【1】promise.setProgressValue(i)で進捗状況を呼び出し元に送信しています。これを
connect(&m_watcher, &QFutureWatcherBase::progressValueChanged,
ui->progressBar, &QProgressBar::setValue);
のようにシグナルprogressValueChanged()をスロットでキャッチすることで、プログレスバーの進捗率を変更することができます。
【2】promise.suspendIfRequested()で、もしQt Concurrent起動側でQFutureWatcher.suspend()を呼んでいた場合は処理を中断します。QFutureWatcher.resume()が呼ばれると処理を再開します。
suspend()はQt6.0から追加された関数です。
【3】QFutureWatcher.cancel()が呼ばれると、promise.isCanceled()がtrueになります。QFutureWatcher.cancel()が呼ばれた場合、処理は途中から再開するのではなく、最初からやり直しになります。
最後に
簡単にですが、Promiseモードについて説明しました。
Qt6には、Qt Concurrent runと同じくらい実装が簡単で、かつ複雑な設定も追加できる「Qt Concurrent task」という機能も実装されています。(こちらのBasicモードについては拙著Qt5/Qt6入門 C++編で説明しています)。
Qt Concurrentに関しては、Qt6でだいぶ良い方向に改善された印象です。
ぜひPromiseモードを使用してみてください。