【问题标题】:Thread and GUI handling线程和 GUI 处理
【发布时间】:2023-03-29 14:20:02
【问题描述】:

在我的应用程序中,线程和 GUI 消息(如 QMessageBox 或新对话框)存在一些问题。为了重现我制作了一个小应用程序来显示问题:

主窗口.cpp

#include <QDebug>
#include "mainwindow.h"
#include "ui_mainwindow.h"

void ThreadAddTree::run() {

    //mClass->addTreeEx();
    bool b = false;
    emit addTree(&b);
}


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

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_pushButton_clicked()
{
    QString path = "";
    mThreadAddTree = new ThreadAddTree(this, path);
    connect(mThreadAddTree, SIGNAL(addTree(bool*)), this, SLOT(on_add_tree(bool*)), Qt::BlockingQueuedConnection);
    //,Qt::DirectConnection
    mThreadAddTree->start();
}

void MainWindow::on_add_tree(bool* newData) {

    QMessageBox::information(this, tr("Information"),
                             tr("Button click!"));

    *newData = true;


}

void MainWindow::addTreeEx()
{
    QMessageBox::information(this, tr("Information"),
                             tr("Button click!"));
}

主窗口.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtWidgets>
#include <QThread>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class ThreadAddTree : public QThread
{
  Q_OBJECT
public:
  ThreadAddTree(class MainWindow *nClass, const QString &path) {
    mPath = path;
    mClass = nClass;
  }
signals:
  void addTree(bool*);
protected:
  void run();
  QString mPath;
  class MainWindow *mClass;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
    friend class ThreadAddTree;

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    void addTreeEx();

private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;

protected:
     ThreadAddTree *mThreadAddTree;


protected Q_SLOTS:
     void on_add_tree(bool* newData);
};
#endif // MAINWINDOW_H

main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

如果我在线程中使用调用:mClass-&gt;addTreeEx(); 应用程序将崩溃,以防非主 GUI 线程。明白了。

因此,我将调用与发出消息 emit addTree(&amp;b); 解耦效果很好。显示消息框并且没有崩溃。

但现在对我来说变得复杂了。我需要在我的应用程序中调用mClass-&gt;addTreeEx();,因为它会执行一些操作。相同的函数也在附加线程之外使用。 但在一种情况下,在线程内运行的mClass-&gt;addTreeEx(); 需要调用Messagebox。

所以我的问题在这里,如何管理,如果从线程调用函数emit addTree(&amp;b);,我可以发出emit addTree(&amp;b);,并且在没有GUI线程的情况下应用程序不会崩溃?

【问题讨论】:

  • 我现在添加了一个更详细的问题,还添加了一个最小示例。

标签: c++ qt


【解决方案1】:

解释代码作者的意图(程度)并开放以进行更正。

  • void addTree(bool*);bool* 类型的信号参数没有多大意义,尤其是对于信号发送方在堆栈上具有布尔变量的情况。要么将其设为void addTree(bool) 并发送立即值,要么仅将void addTree() 并将插槽侧的布尔原子标志处理为std::atomic_bool thread_safe_flag;,因此检查该标志将尽可能实际if (thread_safe_flag)。但是这样的处理需要更多的东西来确保在正确的时间传递的信号与值同步。不过,这与 Qt 无关:Why do I need to acquire a lock to modify a shared "atomic" variable before notifying condition_variable 可以在有或没有 Qt 的情况下完成。

  • UI线程上的消息框阻止其他消息框出现的问题(这再次是作者对代码问题的解释)。我们显然需要一个句柄来操作message box,比如说,关闭它,以防它已经打开:

    QMessageBox* m_msgBoxPtr{nullptr};
    std::atomic_bool m_threadSafeFlag;
    
    void UI_Class::mySlotToHandleMsgBox()
    {
       if (m_msgBoxPtr != nullptr) {
           m_msgBoxPtr->close();
           m_msgBoxPtr->deleteLater();
           m_msgBoxPtr = nullptr;
           mySlotToHandleMsgBox();
       }
       else {
           m_msgBoxPtr = new QMessageBox(QMessageBox::Information, title, message);
           m_msgBoxPtr->exec(); // assuming we want modal dialog as QMessageBox::information()
           // otherwise do m_msgBoxPtr->show()
    
           // if this is set on UI thread only then and no
           // "waits" for it on other threads then it being atomic is enough;
           // then don't bother with any sync "complications"
           m_threadSafeFlag = true;
       }
    }
    

【讨论】:

    【解决方案2】:

    mainworker.h

    #ifndef MAINWORKER_H
    #define MAINWORKER_H
    
    #include <QObject>
    
    class MainWorker : public QObject
    {
        Q_OBJECT
    
    signals:
        void completed(void);
    public slots:
        void run(void);
    };
    
    #endif // MAINWORKER_H
    

    mainworker.cpp

    #include "mainworker.h"
    #include <QThread>
    
    void MainWorker::run(void)
    {
        QThread::sleep(1);
        emit completed();
    }
    

    主线程.h

    #ifndef MAINTHREAD_H
    #define MAINTHREAD_H
    
    #include <QThread>
    
    class MainThread : public QThread
    {
        Q_OBJECT
    
    public:
        MainThread(void);
    };
    
    #endif // MAINTHREAD_H
    

    主线程.cpp

    #include "mainthread.h"
    
    MainThread::MainThread(void)
        : QThread(nullptr)
    {
    }
    

    主窗口.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QPushButton>
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(void);
    signals:
        void runJob(void);
    public slots:
        void jobCompleted(void);
    private:
        QPushButton m_runButton;
    };
    
    #endif // MAINWINDOW_H
    

    主窗口.cpp

    #include "mainwindow.h"
    #include <QMessageBox>
    
    MainWindow::MainWindow(void)
        : QMainWindow(nullptr),
          m_runButton(this)
    {
        connect(&m_runButton, SIGNAL(released()), this, SIGNAL(runJob()));
        m_runButton.setText("RUN!");
        setCentralWidget(&m_runButton);
    }
    
    void MainWindow::jobCompleted(void)
    {
        QMessageBox::information(this, tr("Info"), tr("Job completed!"));
    }
    

    main.cpp

    #include <QApplication>
    #include "mainwindow.h"
    #include "mainthread.h"
    #include "mainworker.h"
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow window;
        MainThread thread;
        MainWorker worker;
    
        worker.connect(&window, SIGNAL(runJob()), &worker, SLOT(run()));
        window.connect(&worker, SIGNAL(completed()), &window, SLOT(jobCompleted()));
    
        worker.moveToThread(&thread);
        thread.start();
        window.show();
    
        int exitCode = a.exec();
    
        thread.quit();
        thread.wait();
    
        return exitCode;
    }
    

    当然,您可以将参数添加到信号和槽,并随时从 GUI 线程调用 jobCompleted() 槽。

    【讨论】:

    • 很抱歉,我不明白如何将您的代码应用到我的示例或最终应用到我的应用中。
    • 纯代码的答案往往无法解释任何事情,从长远来看它们没有用处。
    • 但是需要在问题范围内做一个回答。在我看来,这段代码毫无意义。对不起。
    • Sorry.BTW:在我的问题范围内没有意义。
    • @ingo 你所有的代码 大问题,我向你展示了如何在对象之间正确使用线程、工作线程和信号,此外,如果你想要多个工作线程,请使用doc.qt.io/qt-5/qthreadpool.html
    猜你喜欢
    • 2012-06-11
    • 1970-01-01
    • 1970-01-01
    • 2011-12-26
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多