严的说法是不准确的。使用 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 更加明确并且总是做正确的事情(不管你如何将来自线程管理器的信号连接到其他类的插槽)。您可以看到我的线程中的循环检查原子变量以查看进程何时被取消,从而处理取消要求。