首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >可以取消并报告进度的QFuture

可以取消并报告进度的QFuture
EN

Stack Overflow用户
提问于 2011-03-24 17:25:08
回答 4查看 13.2K关注 0票数 18

QFuture类有诸如cancel()progressValue()等方法,这些方法显然可以通过QFutureWatcher进行监视。但是,QtConcurrent::run()的文档如下:

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

我一直在徒劳地寻找哪种方法实际上可以创建一个可以被取消的QFuture,并报告单个长时间运行的操作的进度。(看起来也许QtConcurrent::map()和类似的函数可以,但我只有一个长期运行的方法。)

(对于熟悉.Net的人来说,类似于BackgroundWorker类。)

有什么可供选择的?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-03-24 18:33:51

对于长期运行的单个任务,QThread可能是您的最佳选择。它没有内置的进度报告或取消功能,所以你将不得不自己滚动。但是对于简单的进度更新来说,这并不难。若要取消任务,请检查可以在任务循环中调用线程时设置的标志。

需要注意的一点是,如果覆盖QThread::run()并将任务放在那里,就不能从那里发出信号,因为QThread对象不是在它运行的线程中创建的,并且不能从正在运行的线程中提取QObject。在这个问题上有一个很好的文章。

票数 1
EN

Stack Overflow用户

发布于 2013-05-24 07:19:34

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

因此,我为我的“可控任务”创建了通用界面:

代码语言:javascript
复制
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用于运行此类任务(这段代码主要来自qtconcurrentrunbase.h,但略有修改):

代码语言:javascript
复制
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<>s的缺失runner类:

代码语言:javascript
复制
class TaskExecutor {
public:
    template <class T>
    static QFuture<T> run(ControllableTask<T>* task) {
        return (new RunControllableTask<T>(task))->start();
    }

};

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

代码语言:javascript
复制
QFututre<int> futureValue = TaskExecutor::run(new SomeControllableTask(inputForThatTask));

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

票数 23
EN

Stack Overflow用户

发布于 2011-04-07 16:30:17

颜的说法是不准确的。使用moveToThread是实现正确行为的一种方法,但它并不是唯一的方法。

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

这里要注意的一点是,构造函数和析构函数在执行的主线程中运行。施工和清理需要在运行中执行。下面是运行方法应该是什么样子的示例:

代码语言:javascript
复制
void MythreadDerrivedClass::run()
{
  constructObjectsOnThread();
  exec();
  destructObjectsOnThread();
  m_waitForStopped.wakeAll();
}

在这里,constructObjectsOnThread将包含您认为属于构造函数的代码。对象将在destructObjectsOnThread中被解除分配。实际的类构造函数将调用exit()方法,从而导致exec()退出。通常,您将使用等待条件坐在析构函数中,直到运行返回为止。

代码语言:javascript
复制
MythreadDerivedClass::~MythreadDerivedClass()
{
  QMutexLocker locker(&m_stopMutex);
  exit();
  m_waitForStopped.wait(locker.mutex(), 1000);
}

因此,构造函数和析构函数再次在父线程中运行。线程拥有的对象必须在run()方法中创建,并在退出运行之前销毁。类析构函数只应该告诉线程退出,并使用QWaitCondition等待线程实际完成执行。注这样做时,QThread派生类的头中确实有Q_OBJECT宏,并且确实包含信号和插槽。

如果您愿意利用KDE库,则另一个选项是KDE的螺纹织布。它是一个更完整的基于任务的多任务实现类似的QtConcurrentRun,因为它利用线程池。对于任何有Qt背景的人来说,这应该是熟悉的。

尽管如此,如果您愿意使用c++11方法来做同样的事情,我将查看std::async。首先,您将不再依赖于Qt,但是api也更清楚地说明了正在发生什么。当MythreadDerivedClass类继承自QThread时,读者会觉得MythreadDerivedClass是一个线程(因为它有继承关系),并且它的所有函数都运行在一个线程上。但是,实际上只有run()方法在线程上运行。std:异步代码更容易正确使用,也更少使用。我们所有的代码最终都是由其他人维护的,从长远来看,这些都很重要。

C++11 /w QT示例:

代码语言:javascript
复制
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更明确,而且总是做正确的事情(不管如何将线程管理器的信号连接到其他类的插槽)。您可以看到,我的线程中的循环检查一个原子变量,以查看进程何时被取消,以便处理取消要求。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/5423058

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档