QFuture类有诸如cancel()、progressValue()等方法,这些方法显然可以通过QFutureWatcher进行监视。但是,QtConcurrent::run()的文档如下:
注意,QtConcurrent::run()返回的QFuture不支持取消、暂停或进度报告。返回的QFuture只能用于查询运行/完成状态和函数的返回值。
我一直在徒劳地寻找哪种方法实际上可以创建一个可以被取消的QFuture,并报告单个长时间运行的操作的进度。(看起来也许QtConcurrent::map()和类似的函数可以,但我只有一个长期运行的方法。)
(对于熟悉.Net的人来说,类似于BackgroundWorker类。)
有什么可供选择的?
发布于 2011-03-24 18:33:51
对于长期运行的单个任务,QThread可能是您的最佳选择。它没有内置的进度报告或取消功能,所以你将不得不自己滚动。但是对于简单的进度更新来说,这并不难。若要取消任务,请检查可以在任务循环中调用线程时设置的标志。
需要注意的一点是,如果覆盖QThread::run()并将任务放在那里,就不能从那里发出信号,因为QThread对象不是在它运行的线程中创建的,并且不能从正在运行的线程中提取QObject。在这个问题上有一个很好的文章。
发布于 2013-05-24 07:19:34
虽然这个问题发布并回答已经有一段时间了,但我决定增加解决这个问题的方法,因为它与这里讨论的内容有很大的不同,我认为可能对其他人有用。首先,我的方法的动机是,当框架已经有一些成熟的类似物时,我通常不喜欢发明自己的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用于运行此类任务(这段代码主要来自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<>s的缺失runner类:
class TaskExecutor {
public:
template <class T>
static QFuture<T> run(ControllableTask<T>* task) {
return (new RunControllableTask<T>(task))->start();
}
};用户应该子类ControllableTask,实现后台例程,有时检查传递给运行的TaskControl实例的方法shouldRun(),然后使用它如下:
QFututre<int> futureValue = TaskExecutor::run(new SomeControllableTask(inputForThatTask));然后,她可以通过调用futureValue.cancel()来取消它,同时要记住取消是优雅的,而不是立即的。
发布于 2011-04-07 16:30:17
颜的说法是不准确的。使用moveToThread是实现正确行为的一种方法,但它并不是唯一的方法。
另一种方法是重写run方法,并创建属于那里的线程的对象。接下来,调用exec()。QThread可以有信号,但要确保连接都排队。此外,对Thread对象的所有调用都应该通过插槽进行,这些插槽也通过排队连接连接。或者,函数调用(它将在调用线程执行线程中运行)可以触发对线程所拥有的对象(在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的螺纹织布。它是一个更完整的基于任务的多任务实现类似的QtConcurrentRun,因为它利用线程池。对于任何有Qt背景的人来说,这应该是熟悉的。
尽管如此,如果您愿意使用c++11方法来做同样的事情,我将查看std::async。首先,您将不再依赖于Qt,但是api也更清楚地说明了正在发生什么。当MythreadDerivedClass类继承自QThread时,读者会觉得MythreadDerivedClass是一个线程(因为它有继承关系),并且它的所有函数都运行在一个线程上。但是,实际上只有run()方法在线程上运行。std:异步代码更容易正确使用,也更少使用。我们所有的代码最终都是由其他人维护的,从长远来看,这些都很重要。
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更明确,而且总是做正确的事情(不管如何将线程管理器的信号连接到其他类的插槽)。您可以看到,我的线程中的循环检查一个原子变量,以查看进程何时被取消,以便处理取消要求。
https://stackoverflow.com/questions/5423058
复制相似问题