18

该类QFuture具有诸如 , 等方法cancel()progressValue()这些显然可以通过QFutureWatcher. 但是,文档为QtConcurrent::run()

请注意,QtConcurrent::run() 返回的 QFuture 不支持取消、暂停或进度报告。返回的 QFuture 只能用于查询函数的运行/结束状态和返回值。

我徒劳地寻找什么方法实际上可以创建一个QFuture可以取消并报告单个长时间运行操作的进度。(看起来可能QtConcurrent::map()和类似的功能可以,但我只有一个单一的、长时间运行的方法。)

(对于那些熟悉 .Net 的人,类似于BackgroundWorker课程。)

有哪些可用选项?

4

5 回答 5

23

虽然这个问题发布和回答已经有一段时间了,但我决定添加我解决这个问题的方法,因为它与这里讨论的内容有很大不同,我认为可能对其他人有用。首先,我的方法的动机是,当框架已经有一些成熟的类似物时,我通常不喜欢发明自己的 API。所以问题是:我们有一个很好的 API 来控制由 QFuture<> 表示的后台计算,但是我们没有支持某些操作的对象。好吧,让我们去做吧。查看 QtConcurrent::run 内部发生的事情会使事情变得更加清晰:制作了一个仿函数,将其包装到 QRunnable 中并在全局线程池中运行。

所以我为我的“可控任务”创建了通用接口:

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;
};

最后是缺少的 runner 类,它将返回我们可控的 QFututre<>s:

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”的东西......它提供了一个叫做 aQPresent和 a 的东西QPresentWatcher

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

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

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

于 2011-11-01T01:58:54.997 回答
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() 方法中创建并在退出运行之前销毁。类析构函数应该只告诉线程退出并使用 QWaitCondition 等待线程实际完成执行。请注意,当这样做时,QThread 派生类在头文件中确实有 Q_OBJECT 宏,并且确实包含信号和插槽。

如果您愿意利用 KDE 库,另一个选择是 KDE 的Thread Weaver。这是一个更完整的基于任务的多任务实现,类似于 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可能是你最好的选择。它没有内置的进度报告或取消功能,因此您必须自己动手。但是对于简单的进度更新来说并不难。要取消任务,请检查可以从任务循环中的调用线程中设置的标志。

需要注意的一件事是,如果您覆盖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 回答