正确终止 QThread

Properly terminate a QThread

本文关键字:QThread 终止      更新时间:2023-10-16

我有一个在后台进行图像采集的工人类。

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 类中,我启动了工作线程并将信号连接到插槽:

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()

打印的消息证明,调用停止,发出工作完成,发出删除后期()。

不同线程上的对象之间的正常信号/插槽连接要求接收对象的线程运行事件循环。

从理论上讲,您的接收方线程确实运行其事件循环,但事件循环正忙于执行start()槽,因为run()永远不会返回。

您要么需要取消阻塞接收器事件循环,要么使用Qt::DirectConnection调用停止槽。

执行后者时,您需要注意该槽现在在发送方线程的上下文中调用,并且您需要保护m_started免受并发访问。

除了使用自己的标志之外,您还可以使用QThread::requestInterruption()QThread::isInterruptionRequested()

添加

QCoreApplication::processEvents();

到你的循环,它会工作。

死锁的原因是对acq::run()的调用会阻塞,并且没有时间在工作线程上执行acq::stop()

在Ralph Tandetzky和Kevin Krammer的帮助下,我终于找到了解决方案。

  • 我没有用m_worker->stop();关闭线程,而是在worker事件循环中使用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类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()进程事件,直到调用退出m_loop.quit()。使用这种方法,我什至不需要m_thread->wait()因为发出workFinished时会调用m_loop.quit()。我不再需要QMetaObject::invokeMethod(m_thread, "quit", Qt::ConnectionType::DirectConnection);

现在它像一个魅力

编辑:这个解决方案非常沉重和丑陋,Qt(https://www.qtdeveloperdays.com/sites/default/files/David%20Johnson%20qthreads.pdf)在我的情况下使用子类和请求中断是最糟糕的。