gpt4 book ai didi

c++ - QThread不会退出

转载 作者:塔克拉玛干 更新时间:2023-11-03 07:17:08 63 4
gpt4 key购买 nike

当我关闭应用程序时,即使线程应该已经结束,线程仍在运行。以下代码将简单地卡在workerThread->wait();行上。

主线程

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);

workerThread = new QThread();
worker = new Worker();

worker->moveToThread(workerThread);

connect(this, SIGNAL(ThreadStopSignal()), worker, SLOT(ThreadStopSlot()));
connect(this, SIGNAL(StartWorkerSignal()), worker, SLOT(RunSlot()));
connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));

workerThread->start();
emit(StartWorkerSignal());
qDebug()<<"Main thread: " << QThread::currentThreadId();
}

MainWindow::~MainWindow()
{
qDebug() << "Asking threads to exit" << QThread::currentThreadId();
emit(ThreadStopSignal());

workerThread->wait();

qDebug() << "thread is dead";
delete ui;
}

worker 实现

#include "worker.h"

#include <QCoreApplication>

Worker::Worker()
{
allowRun = true;
}

void Worker::RunSlot()
{
qDebug() << "Starting";
int i = 0;
while(allowRun)
{
QThread::msleep(1000);
QCoreApplication::processEvents();
qDebug() << i++ << QThread::currentThreadId();
}
emit finished();
qDebug() << "Done counting";
}

void Worker::ThreadStopSlot()
{
allowRun = false;
qDebug() << "Ending HID WORKER" << QThread::currentThreadId();
}

典型运行将产生以下输出(Qt 5.1.1 clang x86_64)*:

Main thread:  0x7fff743b2300 
Starting
0 0x10b6e1000
1 0x10b6e1000
2 0x10b6e1000
3 0x10b6e1000
4 0x10b6e1000
5 0x10b6e1000
6 0x10b6e1000
7 0x10b6e1000
8 0x10b6e1000
9 0x10b6e1000
10 0x10b6e1000
11 0x10b6e1000
Asking threads to exit 0x7fff743b2300
Ending HID WORKER 0x10b6e1000
12 0x10b6e1000
Done counting

但是,仅在没有UI的情况下,该应用程序仍将运行。它有时会崩溃,从而导致Apple send-crash-report对话框打开。

*是的,我目前停留在较旧的Qt版本上。但是,我已经在较新的版本上对此进行了测试,并获得了相似的结果。

最佳答案

问题很简单:当您用wait完成线程时,您将阻塞主线程中的事件循环。但是,与此同时,事件循环必须接收workerThread->quit()调用。因此,您将陷入僵局。
简单的解决方法是在对该线程进行quit()之前显式对它进行wait()。您可以通过修复QThread固有的损坏来实现此目的。有关Thread类的安全实现,请参见下文。然后,您可以在MainWindow的析构函数中简单地破坏线程。
the,代码是有一些反模式和伏都教的。

  • workerThreadworker都不应该是指针。这是过早的悲观化,因为您添加了额外的堆分配和额外的间接层。这是没有意义的,它会迫使您执行不需要任何内容​​的手动内存管理。
  • threadStopSlot是线程安全的,没有理由从辅助线程的事件循环中调用它。您可以直接调用它。这样做时,您无需在QCoreApplication::processEvents中调用runSlot。执行此操作时,您将重新进入事件循环,并且突然在该线程中运行的所有对象都受到重入要求的限制,因此必须对此进行审核。坏主意-不要这样做。
  • 因为您可能希望让事件循环在工作线程中运行,所以您应该反转控件:而不是将控件保留在runSlot中,而应将其保留在事件循环中,并让事件循环重复调用runSlot。这就是零超时计时器的成语。
  • 初始化程序列表产生了惯用的C++。使用它们。
  • emit旨在作为前缀,而不是函数。您emit fooSignal(),而不是emit(fooSignal())。这是样式问题,是真的,但是emit是出于文档目的。它仅用于人类食用,当我们不将它们包裹在多余的括号中时,我们人类会更轻松地阅读。如果您不关心文档方面,则完全不要使用emit。信号是机器生成的实现的常规方法。您无需以任何特殊方式调用它们。
  • 由于您使用Qt 5,因此应使用经过编译时检查的connect语法。这不需要C++ 11编译器-除非您也使用lambdas,否则不需要。
  • 在窗口的析构函数中要求线程退出可能是一个坏主意,因为随着工作线程的退出,您可能在主线程中还有其他事情要做-这些事情必须使事件循环运行。
    销毁窗口的唯一方法是具有WA_DeleteOnClose属性,或者退出主事件循环并在退出main()时销毁该窗口。您应该捕获该窗口的关闭事件,并使其处于 Activity 状态,以便仅在完成所有应完成的操作后才删除该窗口。
  • 可以将线程本身输出,而不是将线程ID输出到qDebug()。您可以利用线程是对象这一事实,并且可以给它们赋予人类可读的名称。然后,您无需手动比较线程ID或地址,只需读取线程的名称即可。

  • 鉴于以上所有内容,如果您要我编写代​​码,请按照以下说明进行操作。
    首先,可以将工作程序功能抽象为工作程序库:
    #include <QtWidgets>

    class WorkerBase : public QObject {
    Q_OBJECT
    Q_PROPERTY(bool active READ isActive WRITE setActive)
    QBasicTimer m_runTimer;
    bool m_active;
    protected:
    void timerEvent(QTimerEvent * ev) {
    if (ev->timerId() != m_runTimer.timerId()) return;
    work();
    }
    virtual void workStarted() {}
    virtual void work() = 0;
    virtual void workEnded() {}
    public:
    WorkerBase(QObject * parent = 0) : QObject(parent), m_active(false) {
    setActive(true);
    }
    /// This method is thread-safe.
    bool isActive() const { return m_active; }
    /// This method is thread-safe.
    void setActive(bool active) {
    QObject source;
    QObject::connect(&source, &QObject::destroyed, this, [this,active]{
    // The functor is executed in the thread context of this object
    if (m_active == active) return;
    if (active) {
    m_runTimer.start(0, this);
    workStarted();
    } else {
    m_runTimer.stop();
    workEnded();
    }
    m_active = active;
    }, thread() ? Qt::QueuedConnection : Qt::DirectConnection);
    }
    ~WorkerBase() {
    Q_ASSERT(QThread::currentThread() == thread() || !thread());
    setActive(false);
    }
    };
    然后,工作人员变得直截了当:
    class Worker : public WorkerBase {
    Q_OBJECT
    int m_runCount;
    protected:
    void workStarted() Q_DECL_OVERRIDE {
    qDebug() << "Starting" << QThread::currentThread();
    }
    void work() Q_DECL_OVERRIDE {
    QThread::msleep(1000);
    ++ m_runCount;
    qDebug() << m_runCount << QThread::currentThread();
    }
    void workEnded() Q_DECL_OVERRIDE {
    qDebug() << "Finishing" << QThread::currentThread();
    emit finished();
    }
    public:
    Worker(QObject * parent = 0) : WorkerBase(parent), m_runCount(0) {}
    Q_SIGNAL void finished();
    };
    最后,我们添加了安全线程实现,以及一个主窗口,该窗口将控制源保留在事件循环中,并允许循环运行,直到一切准备就绪,可以销毁该窗口为止:
    class Thread : public QThread {
    using QThread::run; // final method
    public:
    Thread(QObject * parent = 0) : QThread(parent) {}
    ~Thread() { quit(); wait(); }
    };

    class MainWindow : public QMainWindow {
    Q_OBJECT
    Worker m_worker;
    Thread m_workerThread;
    QLabel m_label;
    protected:
    void closeEvent(QCloseEvent * ev) {
    if (m_worker.isActive()) {
    m_worker.setActive(false);
    ev->ignore();
    } else
    ev->accept();
    }
    public:
    MainWindow(QWidget * parent = 0) : QMainWindow(parent),
    m_label("Hello :)\nClose the window to quit.")
    {
    setCentralWidget(&m_label);
    m_workerThread.setObjectName("m_worker");
    m_worker.moveToThread(&m_workerThread);
    connect(&m_worker, &Worker::finished, this, &QWidget::close);
    m_workerThread.start();
    qDebug() << "Main thread:" << QThread::currentThread();
    }
    ~MainWindow() {
    qDebug() << __FUNCTION__ << QThread::currentThread();
    }
    };

    int main(int argc, char ** argv)
    {
    QApplication a(argc, argv);
    QThread::currentThread()->setObjectName("main");
    MainWindow w;
    w.show();
    w.setAttribute(Qt::WA_QuitOnClose);
    return a.exec();
    }

    #include "main.moc"
    请注意,您的方法明显缺少过多的信号/插槽。您需要关心的是工作对象完成后,您可以关闭窗口并将所有对象撕成碎片。
    请注意,声明顺序很重要。在这方面,C++的语义不是随机的:顺序具有含义。工作线程必须在工作对象之后声明,因为它们将以相反的顺序被破坏。因此,线程首先被退出并销毁。到那时,您可以使用 m_worker->thread() == nullptr从任何线程(包括主线程)中销毁该工作程序。如果工作线程仍然存在,那将是一个错误-然后,您需要在其自己的线程中销毁该工作线程。
    输出:
    Main thread: QThread(0x7fa59b501180, name = "main")
    Starting QThread(0x7fff5e053af8, name = "m_worker")
    1 QThread(0x7fff5e053af8, name = "m_worker")
    2 QThread(0x7fff5e053af8, name = "m_worker")
    3 QThread(0x7fff5e053af8, name = "m_worker")
    Finishing QThread(0x7fff5e053af8, name = "m_worker")
    ~MainWindow QThread(0x7fa59b501180, name = "main")

    关于c++ - QThread不会退出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31972037/

    63 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com