Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What are the problem?
@argama147

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

初めに

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
1
Help us understand the problem. What are the problem?