正确终止一个 QThread
Properly terminate a QThread
我有一个工作人员 class 在后台进行图像采集。
void acq::run ()
{
while (m_started)
{
blocking_call();
}
emit workFinished();
}
void acq::start ()
{
m_started = true;
run();
}
void acq::stop ()
{
m_started = false;
}
start ()
; stop ()
是插槽,workFinished
是信号。
所以在我的 UI Class 中,我启动了 worker 并将信号连接到插槽:
m_thread = new QThread;
m_worker = new acq();
m_worker->moveToThread(m_thread);
// When the thread starts, then start the acquisition.
connect(m_thread, SIGNAL (started ()), m_worker, SLOT (start ()));
// When the worker has finished, then close the thread
connect(m_worker, SIGNAL(workFinished()), m_thread, SLOT(quit()));
m_thread->start();
至此,我实现了插槽,closeEvent
void UIClass::closeEvent (QCloseEvent *event)
{
m_worker->stop(); // tell the worker to close
m_thread->wait(); // wait until the m_thread.quit() function is called
event->accept(); // quit the window
}
不幸的是,m_thread->wait()
正在阻塞。即使发出信号quit()
谢谢
编辑:
我添加了这两个连接:
connect(m_worker, SIGNAL(workFinished()), m_worker, SLOT(deleteLater()));
connect(m_thread, SIGNAL(finished()), m_thread, SLOT(deleteLater()));
和 Qdebug 到 acq::~acq()
打印的消息证明调用了 stop,发出了 workFinished,发出了 deleteLater()。
不同线程上的对象之间的正常 signal/slot 连接要求接收方对象的线程 运行 是一个事件循环。
您的接收器线程在理论上 运行 它的事件循环,但事件循环正忙于执行 start()
槽,因为 run()
从不 returns.
您需要解除阻塞接收器事件循环或使用 Qt::DirectConnection
调用停止槽。
在执行后者时,您需要注意插槽现在在发送方线程的上下文中被调用,并且您需要保护 m_started
免受并发访问。
除了使用您自己的标志,您还可以使用 QThread::requestInterruption()
和 QThread::isInterruptionRequested()
添加
QCoreApplication::processEvents();
到你的循环,它会工作。
死锁的原因是对 acq::run()
的调用阻塞并且没有为 acq::stop()
在工作线程上执行留出时间。
在 Ralph Tandetzky 和 Kevin Krammer 的帮助下,我终于找到了解决方案。
我没有使用 m_worker->stop();
关闭线程,而是在工作事件循环中使用 QMetaObject::invokeMethod(m_worker, "stop", Qt::ConnectionType::QueuedConnection);
和 QCoreApplication::processEvents();
。行为不会改变,但我希望它能防止竞争条件或其他问题。
我没有使用:connect(m_worker, SIGNAL(workFinished()), m_thread, SLOT(quit()));
,而是使用自定义插槽:
connect(m_worker, &Acq::workFinished, [=]
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
QMetaObject::invokeMethod(m_thread, "quit", Qt::ConnectionType::DirectConnection);
});
我们使用 DirectConnection 因为我们在无限循环之外,所以事件没有被处理。
有了这个,我遇到了最后一个问题。 m_thread->wait
正在阻塞,我必须读取事件,否则我的自定义插槽将永远不会被调用。所以在我的 UI Class QEventLoop m_loop
.
中添加了一个事件循环
就在 m_thread->wait()
之前,我写了 m_loop.exec();
最后,在我的自定义插槽中,我放了 m_loop.quit()
connect(m_worker, &Acq::workFinished, [=]
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
QMetaObject::invokeMethod(m_thread, "quit", Qt::ConnectionType::DirectConnection);
m_loop.quit();
});
m_loop.exec()
处理事件直到调用 quit m_loop.quit()
。使用该方法,我什至不需要 m_thread->wait()
因为 m_loop.quit()
在发出 workFinished
时被调用。我不需要QMetaObject::invokeMethod(m_thread, "quit", Qt::ConnectionType::DirectConnection);
了
现在它就像一个魅力
编辑:这个解决方案非常笨重和丑陋,Qt (https://www.qtdeveloperdays.com/sites/default/files/David%20Johnson%20qthreads.pdf) 建议在我的案例中使用子类和 requestInteruption。
我有一个工作人员 class 在后台进行图像采集。
void acq::run ()
{
while (m_started)
{
blocking_call();
}
emit workFinished();
}
void acq::start ()
{
m_started = true;
run();
}
void acq::stop ()
{
m_started = false;
}
start ()
; stop ()
是插槽,workFinished
是信号。
所以在我的 UI Class 中,我启动了 worker 并将信号连接到插槽:
m_thread = new QThread;
m_worker = new acq();
m_worker->moveToThread(m_thread);
// When the thread starts, then start the acquisition.
connect(m_thread, SIGNAL (started ()), m_worker, SLOT (start ()));
// When the worker has finished, then close the thread
connect(m_worker, SIGNAL(workFinished()), m_thread, SLOT(quit()));
m_thread->start();
至此,我实现了插槽,closeEvent
void UIClass::closeEvent (QCloseEvent *event)
{
m_worker->stop(); // tell the worker to close
m_thread->wait(); // wait until the m_thread.quit() function is called
event->accept(); // quit the window
}
不幸的是,m_thread->wait()
正在阻塞。即使发出信号quit()
谢谢
编辑:
我添加了这两个连接:
connect(m_worker, SIGNAL(workFinished()), m_worker, SLOT(deleteLater()));
connect(m_thread, SIGNAL(finished()), m_thread, SLOT(deleteLater()));
和 Qdebug 到 acq::~acq()
打印的消息证明调用了 stop,发出了 workFinished,发出了 deleteLater()。
不同线程上的对象之间的正常 signal/slot 连接要求接收方对象的线程 运行 是一个事件循环。
您的接收器线程在理论上 运行 它的事件循环,但事件循环正忙于执行 start()
槽,因为 run()
从不 returns.
您需要解除阻塞接收器事件循环或使用 Qt::DirectConnection
调用停止槽。
在执行后者时,您需要注意插槽现在在发送方线程的上下文中被调用,并且您需要保护 m_started
免受并发访问。
除了使用您自己的标志,您还可以使用 QThread::requestInterruption()
和 QThread::isInterruptionRequested()
添加
QCoreApplication::processEvents();
到你的循环,它会工作。
死锁的原因是对 acq::run()
的调用阻塞并且没有为 acq::stop()
在工作线程上执行留出时间。
在 Ralph Tandetzky 和 Kevin Krammer 的帮助下,我终于找到了解决方案。
我没有使用
m_worker->stop();
关闭线程,而是在工作事件循环中使用QMetaObject::invokeMethod(m_worker, "stop", Qt::ConnectionType::QueuedConnection);
和QCoreApplication::processEvents();
。行为不会改变,但我希望它能防止竞争条件或其他问题。我没有使用:
connect(m_worker, SIGNAL(workFinished()), m_thread, SLOT(quit()));
,而是使用自定义插槽:connect(m_worker, &Acq::workFinished, [=] { std::this_thread::sleep_for(std::chrono::milliseconds(100)); QMetaObject::invokeMethod(m_thread, "quit", Qt::ConnectionType::DirectConnection); });
我们使用 DirectConnection 因为我们在无限循环之外,所以事件没有被处理。
有了这个,我遇到了最后一个问题。
m_thread->wait
正在阻塞,我必须读取事件,否则我的自定义插槽将永远不会被调用。所以在我的 UI ClassQEventLoop m_loop
.
中添加了一个事件循环 就在m_thread->wait()
之前,我写了m_loop.exec();
最后,在我的自定义插槽中,我放了m_loop.quit()
connect(m_worker, &Acq::workFinished, [=] { std::this_thread::sleep_for(std::chrono::milliseconds(100)); QMetaObject::invokeMethod(m_thread, "quit", Qt::ConnectionType::DirectConnection); m_loop.quit(); });
m_loop.exec()
处理事件直到调用 quitm_loop.quit()
。使用该方法,我什至不需要m_thread->wait()
因为m_loop.quit()
在发出workFinished
时被调用。我不需要QMetaObject::invokeMethod(m_thread, "quit", Qt::ConnectionType::DirectConnection);
了
现在它就像一个魅力
编辑:这个解决方案非常笨重和丑陋,Qt (https://www.qtdeveloperdays.com/sites/default/files/David%20Johnson%20qthreads.pdf) 建议在我的案例中使用子类和 requestInteruption。