QFuture可以取消并报告进度

时间:2011-03-24 17:25:08

标签: multithreading qt qtconcurrent qfuture

QFuture类包含cancel()progressValue()等方法。显然可以通过QFutureWatcher监控这些方法。但是,QtConcurrent::run()的文档为:

  

请注意QFuture返回   QtConcurrent :: run()不支持   取消,暂停或进步   报告。 QFuture返回了可以   仅用于查询   运行/完成状态和返回   功能的价值。

我认为实际上可以创建一个可以取消的QFuture的方法并报告单个长时间运行的操作的进度是徒劳的。 (看起来可能是QtConcurrent::map()和类似的函数可以,但我只有一个长期运行的方法。)

(对于那些熟悉.Net的人,比如BackgroundWorker类。)

有哪些选择?

5 个答案:

答案 0 :(得分:18)

虽然已经有一段时间了,因为这个问题已经发布并回答了,我决定加入解决这个问题的方法,因为它与这里讨论的内容有很大不同,我认为对其他人可能有用。首先,我的方法的动机是,当框架已经有一些成熟的类比时,我通常不喜欢发明自己的API。所以问题是:我们有一个很好的API来控制由QFuture<>表示的后台计算,但我们没有支持某些操作的对象。好吧,我们来做吧。查看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&lt;&gt; s:

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

};

用户应该子类化ControllableTask,实现后台例程,有时检查传递给TaskControl实例的TaskRtrol实例的方法(运行(TaskControl&amp;)),然后使用它:

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

然后她可以通过调用futureValue.cancel()取消它,同时记住取消是优雅而不是立即。

答案 1 :(得分:3)

我刚才解决了这个问题,制作了一个名为“Thinker-Qt”的东西......它提供了一个名为QPresentQPresentWatcher的东西:

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

它仍然相当阿尔法,我一直想回去修补它(并且很快就需要这样做)。在我的网站上有一个幻灯片等。我还记录了如何改变Mandelbrot使用它。

如果你想看看和/或贡献,它是开源和LGPL。 :)

答案 2 :(得分:2)

严的陈述是不准确的。使用moveToThread是实现正确行为的一种方法,但它不是唯一的方法。

另一种方法是覆盖run方法并创建由该线程拥有的对象。接下来,您调用exec()。 QThread可以有信号,但要确保连接都是排队的。此外,对Thread对象的所有调用都应该通过也通过Queued连接连接的插槽。或者,函数调用(将在调用者执行线程中运行)可以触发信号到线程所拥有的对象(在run方法中创建),同样需要对连接进行排队。

这里需要注意的一点是构造函数和析构函数是在执行的主线程中运行的。需要在运行中进行构造和清理。以下是您的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()方法中创建线程拥有的对象,并在退出run之前将其销毁。类析构函数应该只告诉线程退出并使用QWaitCondition来等待线程实际完成执行。注意,当这样做时,QThread派生类在标题中确实有Q_OBJECT宏,并且包含信号和槽。

另一个选择,如果你愿意利用KDE库,就是KDE的Thread Weaver。这是一个更完整的基于任务的多任务实现,类似于QtConcurrentRun,它利用了一个线程池。 Qt背景中的任何人都应该熟悉它。

那就是说,如果你对c ++ 11做同样事情的方法持开放态度,我会看std::async。首先,你将不再对Qt有任何依赖,但api也会更清楚地说明发生了什么。使用继承自QThread的MythreadDerivedClass类,读者得到的印象是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更明确,并且始终做正确的事情(无论你如何将线程管理器的信号连接到其他类的插槽)。您可以看到我的线程中的循环检查原子变量以查看进程何时被取消,以便处理取消要求。

答案 3 :(得分:1)

对于长时间运行的单项任务,QThread可能是您最好的选择。它没有内置进度报告或取消功能,因此您必须自己动手。但是对于简单的进度更新,并不是那么难。要取消该任务,请检查可以通过任务循环中的调用线程设置的标志。

有一点需要注意的是,如果你覆盖QThread::run()并将你的任务放在那里,你就不能从那里发出信号,因为QThread对象不是在它运行的线程中创建的,你不能拉来自正在运行的线程的QObject。这个issue有一个很好的写作。

答案 4 :(得分: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(); });
}