同時実行

QtConcurrent::run() 関数は、別のスレッドで関数を実行します。関数の戻り値は、QFuture API から利用できます。

QtConcurrent::run() はオーバーロードされたメソッドです。これらのオーバーロードは、わずかに異なるモードと考えることができます。基本モードでは、QtConcurrent::run() に渡された関数は、呼び出し元に単一の計算結果のみを報告します。プロミス付き実行モードでは、QtConcurrent::run() に渡された関数は、追加のQPromise API を使用することができます。この API を使用すると、複数の結果報告、進行状況の報告、呼び出し元の要求による計算の一時停止、呼び出し元の要求による計算の停止が可能になります。

この関数は Qt Concurrent フレームワークの一部です。

コンカレント実行(基本モード)

QtConcurrent::run() に渡された関数は、その戻り値を通して結果を報告することができます。

別スレッドでの関数の実行

別のスレッドで関数を実行するには、QtConcurrent::run() を使用します:

extern void aFunction();
QFuture<void> future = QtConcurrent::run(aFunction);

これは、デフォルトのQThreadPool から取得した別のスレッドでaFunction を実行します。QFutureQFutureWatcher クラスを使用して、関数の状態を監視することができます。

専用のスレッド・プールを使用するには、最初の引数にQThreadPool を渡します:

extern void aFunction();
QThreadPool pool;
QFuture<void> future = QtConcurrent::run(&pool, aFunction);

関数への引数の渡し方

関数に引数を渡すには、QtConcurrent::run() 呼び出しの関数名の直後に引数を追加します。例えば

extern void aFunctionWithArguments(int arg1, double arg2, const QString &string);

int integer = ...;
double floatingPoint = ...;
QString string = ...;

QFuture<void> future = QtConcurrent::run(aFunctionWithArguments, integer, floatingPoint, string);

各引数のコピーは、QtConcurrent::run() が呼び出された時点で作成され、これらの値は、関数の実行を開始するときにスレッドに渡されます。QtConcurrent::run() を呼び出した後に引数に加えられた変更は、スレッドからは見えません

QtConcurrent::run は、オーバーロードされた関数を直接呼び出すことをサポートしていないことに注意してください。例えば、以下のコードはコンパイルできません:

void foo(int arg);
void foo(int arg1, int arg2);
...
QFuture<void> future = QtConcurrent::run(foo, 42);

最も簡単な回避策は、ラムダを通してオーバーロード関数を呼び出すことです:

QFuture<void> future = QtConcurrent::run([] { foo(42); });

あるいは、static_cast を使って、どのオーバーロードを選ぶかをコンパイラに指示することもできます:

QFuture<void> future = QtConcurrent::run(static_cast<void(*)(int)>(foo), 42);

またはqOverload

QFuture<void> future = QtConcurrent::run(qOverload<int>(foo), 42);

関数からの戻り値

関数からの戻り値は、QFuture

extern QString functionReturningAString();
QFuture<QString> future = QtConcurrent::run(functionReturningAString);
...
QString result = future.result();

結果が不要な場合(例えば、関数がvoid を返すなど)は、QThreadPool::start() オーバーロードを使用して関数オブジェクトを取得する方が効率的です。

上で説明したように、引数の受け渡しはこのように行います:

extern QString someFunction(const QByteArray &input);

QByteArray bytearray = ...;

QFuture<QString> future = QtConcurrent::run(someFunction, bytearray);
...
QString result = future.result();

QFuture::result() 関数は、ブロックして結果が利用可能になるのを待つことに注意してください。関数の実行が終了し、結果が利用可能になったときに通知を受け取るには、QFutureWatcher を使用してください。

APIの追加機能

メンバ関数の使用

QtConcurrent::run() は、メンバ関数へのポインタも受け付けます。最初の引数は const 参照か、クラスのインスタンスへのポインタでなければなりません。const参照で渡すと、constメンバ関数を呼び出すときに便利です。ポインタで渡すと、インスタンスを変更する非constメンバ関数を呼び出すときに便利です。

例えば、QByteArray::split (constメンバ関数)を別スレッドで呼び出すと、このようになります:

// call 'QList<QByteArray>  QByteArray::split(char sep) const' in a separate thread
QByteArray bytearray = "hello world";
QFuture<QList<QByteArray> > future = QtConcurrent::run(&QByteArray::split, bytearray, ' ');
...
QList<QByteArray> result = future.result();

constでないメンバ関数の呼び出しは次のようになります:

// call 'void QImage::invertPixels(InvertMode mode)' in a separate thread
QImage image = ...;
QFuture<void> future = QtConcurrent::run(&QImage::invertPixels, &image, QImage::InvertRgba);
...
future.waitForFinished();
// At this point, the pixels in 'image' have been inverted

ラムダ関数の使用

ラムダ関数の呼び出しは次のように行われます:

QFuture<void> future = QtConcurrent::run([=]() {
    // Code in this block will run in another thread
});
...

参照渡しされたオブジェクトを変更する関数の呼び出しは次のように行います:

static void addOne(int &n) { ++n; }
...
int n = 42;
QtConcurrent::run(&addOne, std::ref(n)).waitForFinished(); // n == 43

呼び出し可能オブジェクトの使用は次のように行う:

struct TestClass
{
    void operator()(int s1) { s = s1; }
    int s = 42;
};

...

TestClass o;

// Modify original object
QtConcurrent::run(std::ref(o), 15).waitForFinished(); // o.s == 15

// Modify a copy of the original object
QtConcurrent::run(o, 42).waitForFinished(); // o.s == 15

// Use a temporary object
QtConcurrent::run(TestClass(), 42).waitForFinished();

// Ill-formed
QtConcurrent::run(&o, 42).waitForFinished(); // compilation error

プロミスによる同時実行

プロミスを使用した実行モードでは、QtConcurrent::run() の基本モードと比較して、実行中のタスクをより制御することができます。実行中のタスクの進捗報告、複数の結果の報告、要求があった場合の実行の一時停止、呼び出し元の要求によるタスクのキャンセルなどが可能です。

必須の QPromise 引数

プロミス付き実行モードで QtConcurrent::run() に渡される関数は、QPromise<T> & 型の追加引数を持つことが期待されます。T は計算結果の型です(QtConcurrent::run() が返すQFuture<T> の型T と一致する必要があります):

extern void aFunction(QPromise<void> &promise);
QFuture<void> future = QtConcurrent::run(aFunction);

promise 引数は QtConcurrent::run() 関数の内部でインスタンス化され、その参照は呼び出されたaFunction に渡されます。

このため、このモードで QtConcurrent::run() を呼び出す際に、ユーザーがインスタンス化したり、明示的に渡したりする必要はありません。QPromise 型の追加引数は、常に関数の引数リストの第 1 引数として表示される必要があります:

extern void aFunction(QPromise<void> &promise, int arg1, const QString &arg2);

int integer = ...;
QString string = ...;

QFuture<void> future = QtConcurrent::run(aFunction, integer, string);

結果の報告

QtConcurrent::run() の基本モードとは対照的に、Run With Promiseモードで QtConcurrent::run() に渡される関数は、常に void 型を返すことが期待されています。結果報告は、QPromise 型の追加引数を通して行われます。のように、複数の結果報告も可能になります:

void helloWorldFunction(QPromise<QString> &promise)
{
    promise.addResult("Hello");
    promise.addResult("world");
}

QFuture<QString> future = QtConcurrent::run(helloWorldFunction);
...
QList<QString> results = future.results();

注意: 計算の開始と終了を示すためにQPromise::start() とQPromise::finish() を呼び出す必要はありません(通常QPromise を使用する場合のように)。QtConcurrent::run()は、実行の開始前と終了後に必ず呼び出します。

実行の一時停止とキャンセル

QPromise API は、要求があれば計算の中断とキャンセルも可能です:

void aFunction(QPromise<int> &promise)
{
    for (int i = 0; i < 100; ++i) {
        promise.suspendIfRequested();
        if (promise.isCanceled())
            return;

        // computes the next result, may be time consuming like 1 second
        const int res = ... ;
        promise.addResult(res);
    }
}

QFuture<int> future = QtConcurrent::run(aFunction);

... // user pressed a pause button after 10 seconds
future.suspend();

... // user pressed a resume button after 10 seconds
future.resume();

... // user pressed a cancel button after 10 seconds
future.cancel();

future.suspend() を呼び出すと、実行中のタスクに実行の保留を要求する。このメソッドを呼び出すと、実行中のタスクは反復ループの次のpromise.suspendIfRequested() 呼び出しの後に一時停止する。この場合、実行中のタスクはpromise.suspendIfRequested() への呼び出しでブロックされる。ブロックされた呼び出しは、future.resume() が呼び出された後にブロックが解除される。内部的には、suspendIfRequested() はブロックを解除するためにwait条件を使用するので、実行中のスレッドは、再開要求が呼び出し元のスレッドから来たかどうかを定期的にチェックするために、ブロックされたときにリソースを浪費する代わりにアイドル状態になる。

最後の行のfuture.cancel() への呼び出しにより、promise.isCanceled() への次の呼び出しはtrue を返し、aFunction はそれ以上結果を報告することなく即座に戻ります。

注意: キャンセル後に計算を停止するためにQPromise::finish() を呼び出す必要はありません(通常QPromise を使用する場合のように)。QtConcurrent::run()は、常に実行終了後に呼び出されます。

進捗報告

結果報告とは別に、タスクの進捗を報告することも可能です:

void aFunction(QPromise<int> &promise)
{
    promise.setProgressRange(0, 100);
    int result = 0;
    for (int i = 0; i < 100; ++i) {
        // computes some part of the task
        const int part = ... ;
        result += part;
        promise.setProgressValue(i);
    }
    promise.addResult(result);
}

QFutureWatcher<int> watcher;
QObject::connect(&watcher, &QFutureWatcher::progressValueChanged, [](int progress){
    ... ; // update GUI with a progress
    qDebug() << "current progress:" << progress;
});
watcher.setFuture(QtConcurrent::run(aFunction));

呼び出し元は、QtConcurrent::run() によって返されたQFutureQFutureWatcher をインストールし、そのprogressValueChanged() シグナルに接続し、それに応じてグラフィカルユーザインタフェースなどを更新します。

オーバーロードされた operator()() による関数の呼び出し

デフォルトでは、QtConcurrent::run() は、Run With Promiseモードでオーバーロードされた operator()() を持つファンクタをサポートしていません。オーバーロードされたファンクタの場合、QtConcurrent::run() に渡されるテンプレートパラメータとして、結果の型を明示的に指定する必要があります:

struct Functor {
    void operator()(QPromise<int> &) { }
    void operator()(QPromise<double> &) { }
};

Functor f;
run<double>(f); // this will select the 2nd overload
// run(f);      // error, both candidate overloads potentially match

©2024 The Qt Company Ltd. 本文書に含まれる文書の著作権は、それぞれの所有者に帰属します。 ここで提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。