LoginSignup
3
2

More than 1 year has passed since last update.

Qt6の新機能 Promiseモードを使ってみる

Last updated at Posted at 2021-07-25

初めに

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の動作を確認することができるサンプルとなっています。

promise.gif
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モードを使用してみてください。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2