18

クラスには、、QFutureなどのメソッドがあります。これらは、。を介して監視できるようです。ただし、次のドキュメントを参照してください。cancel()progressValue()QFutureWatcherQtConcurrent::run()

QtConcurrent :: run()によって返されるQFutureは、キャンセル、一時停止、または進行状況のレポートをサポートしていないことに注意してください。返されたQFutureは、実行中/終了ステータスと関数の戻り値を照会するためにのみ使用できます。

キャンセルして1回の長時間実行操作の進行状況を報告できる方法を実際に作成できる方法については、無駄に調べました。QFuture(多分、同様の機能が可能であるように見えQtConcurrent::map()ますが、私は単一の、長時間実行されるメソッドを持っています。)

(.Netに精通している人のために、BackgroundWorkerクラスのようなもの。)

どのようなオプションが利用できますか?

4

5 に答える 5

23

この質問が投稿されて回答されてからしばらく経ちましたが、ここで説明したものとはかなり異なり、他の誰かに役立つかもしれないので、この問題を解決する方法を追加することにしました。まず、私のアプローチの動機は、フレームワークにすでにいくつかの成熟した類似体がある場合、通常、独自のAPIを発明することを好まないということです。したがって、問題は次のとおりです。QFuture<>で表されるバックグラウンド計算を制御するための優れたAPIがありますが、一部の操作をサポートするオブジェクトがありません。さて、やってみましょう。QtConcurrent :: run内で何が起こっているかを見ると、物事がはるかに明確になります。ファンクターが作成され、QRunnableにラップされ、グローバルThreadPoolで実行されます。

そこで、「制御可能なタスク」用の汎用インターフェースを作成しました。

class TaskControl
{
public:
    TaskControl(QFutureInterfaceBase *f) : fu(f) {  }
    bool shouldRun() const { return !fu->isCanceled(); }
private:
    QFutureInterfaceBase *fu;
};

template <class T>
class ControllableTask
{
public:
    virtual ~ControllableTask() {}
    virtual T run(TaskControl& control) = 0;
};

次に、qtconcurrentrunbase.hで作成された内容に従って、この種のタスクを実行するためにq-runnableを作成しました(このコードは主にqtconcurrentrunbase.hからのものですが、わずかに変更されています)。

template <typename T>
class RunControllableTask : public QFutureInterface<T> , public QRunnable
{
public:
    RunControllableTask(ControllableTask<T>* tsk) : task(tsk) { }
    virtial ~RunControllableTask() { delete task; }

    QFuture<T> start()
    {
        this->setRunnable(this);
        this->reportStarted();
        QFuture<T> future = this->future();
        QThreadPool::globalInstance()->start(this, /*m_priority*/ 0);
        return future;
    }

    void run()
    {
        if (this->isCanceled()) {
            this->reportFinished();
            return;
        }
        TaskControl control(this);
        result = this->task->run(control);
        if (!this->isCanceled()) {
            this->reportResult(result);
        }
        this->reportFinished();
    }

    T  result;
    ControllableTask<T> *task;
};

そして最後に、制御可能なQFututre<>を返す欠落しているランナークラス:

class TaskExecutor {
public:
    template <class T>
    static QFuture<T> run(ControllableTask<T>* task) {
        return (new RunControllableTask<T>(task))->start();
    }

};

ユーザーは、ControllableTaskをサブレーザー化し、run(TaskControl&)に渡されたTaskControlインスタンスのメソッドshouldRun()をチェックするバックグラウンドルーチンを実装して、次のように使用する必要があります。

QFututre<int> futureValue = TaskExecutor::run(new SomeControllableTask(inputForThatTask));

次に、キャンセルは適切であり、即時ではないことを念頭に置いて、futureValue.cancel()を呼び出すことでキャンセルできます。

于 2013-05-24T07:19:34.277 に答える
3

私は少し前にこの正確な問題に取り組み、「Thinker-Qt」と呼ばれるものを作成しました...それはaQPresentQPresentWatcher:と呼ばれるものを提供します

http://hostilefork.com/thinker-qt/

それはまだかなりアルファ版であり、私は戻ってそれをいじくり回すことを意味してきました(そしてすぐにそうする必要があります)。私のサイトにはスライドデッキなどがあります。また、マンデルブロを使用するように変更する方法についても説明しました。

ご覧になりたい、または貢献したい場合は、オープンソースおよびLGPLです。:)

于 2011-11-01T01:58:54.997 に答える
2

ヤンの発言は不正確です。moveToThreadを使用することは、適切な動作を実現する1つの方法ですが、唯一の方法ではありません。

別の方法は、runメソッドをオーバーライドして、そこのスレッドが所有するオブジェクトを作成することです。次に、exec()を呼び出します。QThreadはシグナルを持つことができますが、接続がすべてキューに入れられていることを確認してください。また、Threadオブジェクトへのすべての呼び出しは、キュー接続を介して接続されているスロットを介して行う必要があります。あるいは、関数呼び出し(実行の呼び出し元スレッドで実行される)は、スレッド(runメソッドで作成される)が所有するオブジェクトへのシグナルをトリガーできます。この場合も、接続をキューに入れる必要があります。

ここで注意すべきことの1つは、コンストラクタとデストラクタが実行のメインスレッドで実行されていることです。建設とクリーンアップは実行中に実行する必要があります。runメソッドがどのようになるかの例を次に示します。

void MythreadDerrivedClass::run()
{
  constructObjectsOnThread();
  exec();
  destructObjectsOnThread();
  m_waitForStopped.wakeAll();
}

ここで、constructObjectsOnThreadには、コンストラクターに属していると思われるコードが含まれます。オブジェクトはdestructObjectsOnThreadで割り当て解除されます。実際のクラスコンストラクターはexit()メソッドを呼び出し、exec()を終了させます。通常、実行が戻るまで、待機条件を使用してデストラクタに留まります。

MythreadDerivedClass::~MythreadDerivedClass()
{
  QMutexLocker locker(&m_stopMutex);
  exit();
  m_waitForStopped.wait(locker.mutex(), 1000);
}

繰り返しになりますが、コンストラクタとデストラクタは親スレッドで実行されています。スレッドが所有するオブジェクトは、run()メソッドで作成し、実行を終了する前に破棄する必要があります。クラスデストラクタは、スレッドに終了するように指示し、QWaitConditionを使用して、スレッドが実際に実行を終了するのを待つ必要があります。このようにすると、QThread派生クラスのヘッダーにQ_OBJECTマクロが含まれ、シグナルとスロットが含まれることに注意してください。

KDEライブラリを活用できる場合の別のオプションは、KDEのThreadWeaverです。これは、スレッドプールを活用するという点で、QtConcurrentRunに似たより完全なタスクベースのマルチタスク実装です。Qtのバックグラウンドを持っている人なら誰でも知っているはずです。

そうは言っても、あなたが同じことをするc ++ 11の方法にオープンであるなら、私はを見るでしょうstd::async。一つには、Qtに依存しなくなりますが、APIは何が起こっているのかをより明確にします。MythreadDerivedClassクラスがQThreadから継承されると、読者はMythreadDerivedClassがスレッドであり(継承関係があるため)、そのすべての関数がスレッド上で実行されているという印象を受けます。ただし、run()実際にスレッドで実行されるのはメソッドのみです。std :: asyncは正しく使用するのが簡単で、落とし穴が少なくなっています。私たちのコードはすべて、最終的には他の誰かによって維持され、これらのようなものは長期的には重要です。

C ++ 11 / w QTの例:

class MyThreadManager {
  Q_OBJECT
public:
  void sndProgress(int percent)
  void startThread();
  void stopThread();
  void cancel() { m_cancelled = true; }
private:
  void workToDo(); 
  std::atomic<bool> m_cancelled;
  future<void> m_threadFuture;
};

MyThreadedManger::startThread() {
  m_cancelled = false;
  std::async(std::launch::async, std::bind(&MyThreadedManger::workToDo, this));
}

MyThreadedManger::stopThread() {
  m_cancelled = true;
  m_threadfuture.wait_for(std::chrono::seconds(3))); // Wait for 3s
}

MyThreadedManger::workToDo() {
  while(!m_cancelled) {
    ... // doWork
    QMetaInvoke::invokeMethod(this, SIGNAL(sndProgress(int)), 
      Qt::QueuedConnection, percentDone); // send progress
  }
}

基本的に、ここで得られるものは、QThreadを使用したコードの外観とworkToDo()それほど変わりませんが、スレッド上でのみ実行され、MyThreadManagerがスレッド自体ではなくスレッドのみを管理していることがより明確になります。また、 MetaInvokeを使用して、進行状況の更新を送信するためのキューに入れられた信号を送信し、進行状況のレポート要件を処理しています。MetaInvokeの使用はより明確であり、常に正しいことを行います(スレッドマネージャーからの他のクラスのスロットにシグナルを接続する方法は関係ありません)。スレッドのループがアトミック変数をチェックして、プロセスがいつキャンセルされるかを確認し、キャンセル要件を処理していることがわかります。

于 2011-04-07T16:30:17.767 に答える
1

長時間実行される単一のタスクの場合、QThreadおそらく最善の策です。進行中のレポート機能やキャンセル機能は組み込まれていないため、独自の機能を使用する必要があります。しかし、単純な進捗状況の更新の場合、それほど難しくはありません。タスクをキャンセルするには、タスクのループでスレッドを呼び出すことから設定できるフラグを確認します。

注意すべき点の1つは、タスクをオーバーライドQThread::run()してそこに配置すると、QThreadオブジェクトが実行中のスレッド内に作成されず、実行中のスレッドからQObjectをプルできないため、そこからシグナルを送信できないことです。この問題については良い記事があります。

于 2011-03-24T18:33:51.697 に答える
1

@Hatter回答をサポートするように改善しFunctorます。

#include <QFutureInterfaceBase>
#include <QtConcurrent>

class CancellationToken
{
public:
    CancellationToken(QFutureInterfaceBase* f = NULL) : m_f(f){ }
    bool isCancellationRequested() const { return m_f != NULL && m_f->isCanceled(); }
private:
    QFutureInterfaceBase* m_f;
};

/*== functor task ==*/
template <typename T, typename Functor>
class RunCancelableFunctorTask : public QtConcurrent::RunFunctionTask<T>
{
public:
    RunCancelableFunctorTask(Functor func) : m_func(func) { }
    void runFunctor() override
    {
        CancellationToken token(this);
        this->result = m_func(token);
    }
private:
    Functor m_func;
};

template <typename Functor>
class RunCancelableFunctorTask<void, Functor> : public QtConcurrent::RunFunctionTask<void>
{
public:
    RunCancelableFunctorTask(Functor func) : m_func(func) { }
    void runFunctor() override
    {
        CancellationToken token(this);
        m_func(token);
    }
private:
    Functor m_func;
};

template <class T>
class HasResultType
{
    typedef char Yes;
    typedef void *No;
    template<typename U> static Yes test(int, const typename U::result_type * = 0);
    template<typename U> static No test(double);
public:
    enum { Value = (sizeof(test<T>(0)) == sizeof(Yes)) };
};

class CancelableTaskExecutor
{
public:
    //function<T or void (const CancellationToken& token)>
    template <typename Functor>
    static auto run(Functor functor)
        -> typename std::enable_if<!HasResultType<Functor>::Value,
                        QFuture<decltype(functor(std::declval<const CancellationToken&>()))>>::type
    {
        typedef decltype(functor(std::declval<const CancellationToken&>())) result_type;
        return (new RunCancelableFunctorTask<result_type, Functor>(functor))->start();
    }
};

ユーザー例:

#include <QDateTime>
#include <QDebug>
#include <QTimer>
#include <QFuture>
void testDemoTask()
{
    QFuture<void> future = CancelableTaskExecutor::run([](const CancellationToken& token){
        //long time task..
        while(!token.isCancellationRequested())
        {
            qDebug() << QDateTime::currentDateTime();
            QThread::msleep(100);
        }
        qDebug() << "cancel demo task!";
    });
    QTimer::singleShot(500, [=]() mutable { future.cancel(); });
}
于 2019-09-07T09:32:50.010 に答える