有两个类:Widget
和Worker
。这是一个原理图代码。
class Widget
{
Worker *m_worker;
QTextEdit *m_edit;
public:
Widget():m_edit(new QTextEdit){}
~Widget()
{
m_worker->ShouldStop = true;
delete *m_worker;
}
void doWork()
{
m_worker = new Worker;
if (!worker->doWork())
m_edit->setText("failed");
}
}
class Worker
{
Worker() : ShouldStop(false){}
public:
bool ShouldStop;
bool doWork()
{
while(true && !ShouldStop)
{
QThread::sleep(1);
QApplication::processEvents();
}
//consider the work undone if a stop was forced
if (ShouldStop)
return false;
}
}
调用Widget
的doWork()
后,在Worker
的doWork()
方法中执行循环。然后关闭小部件,并在对processEvents()
的一次调用中调用其析构函数。然后执行返回到Worker
的doWork()
。它现在检查ShouldStop
,并返回到Widget
的doWork()
,并尝试向m_edit
添加一些东西。然而,Widget
对象已经死亡。
问题:
- 如何干净地删除Worker ?
- 避免这种相互作用的最佳设计是什么?
理想情况下,工作线程应该通过信号和插槽机制返回数据,而不是直接访问原始对象。实际上,创建一个线程并避免调用QApplication::processEvents()
是避免这个问题的第一种方法。
另外,当你想在一个新线程中启动一个worker时,你应该考虑使用这样的设计。您可以创建一个泛型的QThread并为其分配一个QObject,而不是创建QThread的子类,如下所示:
Worker *worker = new Worker;
QThread *workerThread = new QThread(this);
connect(workerThread, &QThread::started, worker, &Worker::doWork);
connect(workerThread, &QThread::finished, worker, &Worker::deleteLater);
worker->moveToThread(workerThread);
// Starts an event loop, and emits workerThread->started()
workerThread->start();
如果您正在使用Qt 5,请考虑围绕此模式重构您的代码。
@Alex的回答绝对是q2的选择。他只是忘了提到如何进行删除(q1)。假设Widget
创建了对象workerThread
,那么这是必要的:
~Widget(){
if(workerThread->isRunning()){
workerThread->terminate(); <--- this line, right there
workerThread->wait():
}
...
}
为什么?因为删除QThread
只是删除线程对象,而不会停止线程的运行,并且可能导致应用程序崩溃。注意,如果使用
connect(workerThread, &QThread::finished, worker, &Worker::deleteLater);
,那么在调用terminate
之后访问worker
是不安全的,因为它会触发finished()
信号,执行worker->deleteLater
,最终删除worker
。
你可以让Widget使用一个信号来通知Widget的所有者为Widget启动Worker。
Owner同时拥有Widget和Worker。Widget告诉Owner某事被触发了,而Owner负责决定Worker的doWork()是否是正确的响应,并负责管理Worker和Widget的生命周期。Worker和Widget彼此不知道对方,但是Owner可能会将它们的一些信号和插槽连接在一起。