如何从 std::thread 更改 GUI?

How can I change GUI from std::thread?

首先,我尝试使用 thread

中的 setVisible()

有一个事件:

void MainWindow::OnShow(){
    // Start OnShow actions
    ui->LoadingBox->setVisible(true);
    std::thread dThread(OnShow_threaded, ui, &(this->settingsMap));
    dThread.join();
}

有函数OnShow_threaded:

void OnShow_threaded(Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap){

    // Connect to server
    bool hasInternet = false;
   
    // If app doesn't have Internet access -> show offline mode
    if (!hasInternet) {
        ui->SettingsLabel->setVisible(true);
    }
}

编译静态程序集时程序崩溃,报错:

ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread 0x0x36c56540. Receiver 'WarningMsg' (of type 'QGroupBox') was created in thread 0x0x341c2fa0", file kernel\qcoreapplication.cpp, line 558

在线:ui->SettingsLabel->setVisible(true);

同时,动态链接时没有出现该错误

您可以在 GitHub

上找到完整的项目

其次,我尝试使用事件。

有函数OnShow_threaded:

void OnShow_threaded(MainWindow* mw, Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap){
    // Connect to server
    bool hasInternet = false;

    // If app doesn't have Internet access -> show offline mode
    if (!hasInternet) {
        MyEvent* event = new MyEvent(EventTypes::InternetConnectionError);
        QCoreApplication::postEvent(mw, event);
        //delete event;
        //delete receiver;
    }
}

有一个事件class:

#ifndef EVENTS_HPP
#define EVENTS_HPP

#include <QEvent>
#include <QString>

enum EventTypes {
    InternetConnectionError,
    Unknown
};

class MyEvent : public QEvent
{
public:
  MyEvent(const EventTypes _type) : QEvent(QEvent::User) {_localType = _type;}
 ~MyEvent() {}

  auto localType() const {return _localType;}


private:
  int _localType;
};

#endif // EVENTS_HPP

有一个事件处理程序:

void MainWindow::events(QEvent *event)
{
    if (event->type() == QEvent::User)
      {
        MyEvent* postedEvent = static_cast<MyEvent*>(event);

        if (postedEvent->localType() == EventTypes::InternetConnectionError){
            ui->WarningMsg->setVisible(true);
            ui->SettingsLabel->setVisible(true);
        }
    }
}

传递参数:

void MainWindow::OnShow(){
    // Start OnShow actions
    ui->LoadingBox->setVisible(true);
    std::thread dThread(OnShow_threaded, this, ui, &(this->settingsMap));
    dThread.detach();
}

有一个 mainwindows hpp 文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QDebug>
#include <QMovie>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
#include <QMessageBox>
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QCoreApplication>
#include <QSaveFile>
#include <QProcess>

#include <thread>
#include <chrono>
#include <unordered_map>
#include <iostream>
#include <fstream>
#include <cstdlib>

#include "settings.hpp"
#include "events.hpp"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

    void OnShow();

private slots:
    void SettingsLabelPressed();

    void on_CloseMsgButton_clicked();

    void on_Settings_SaveButton_clicked();

    void on_Settings_UseTranslation_stateChanged(int arg1);

protected:

    void events(QEvent* event);

private:
    Ui::MainWindow *ui;
    std::unordered_map<QString,QString> settingsMap;
};

void OnShow_threaded(MainWindow* mw, Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap);

#endif // MAINWINDOW_H

但是事件没有执行。
我做错了什么?
以及如何从另一个线程正确更改 GUI?


З.Ы.抱歉我的英语不好,我来自俄罗斯....


在 Qt 中,与在许多其他 GUI 框架中一样,GUI 可能仅从主线程更新。这意味着如果您想从另一个线程更新 GUI,您必须将其传达给主线程,而主线程又会更新 GUI。

有关更多详细信息,请参阅这些文章:


其他语言的其他资源:Правильная работа с потоками в Qt.

正如您在评论中要求 QThread 提供的演示,这里是。

作为 GUI,我有一个带有两个简单按钮的主窗口,我想显示、隐藏带有 QThread 的大按钮(而不只是点击插槽)我发出一个中间值从点击到 hide/show 按钮的信号。

QThread的作用只是用参数truefalsesigShowHide发出信号。

主 UI 线程通过调用插槽 onShowHideButtonThreaded 显示或隐藏按钮来处理此信号,插槽 onShowHideButtonThreaded 对信号 sigShowHide

作出反应

代码文件如下:

mainwindows.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

signals:
        
       void sigShowHide(bool);

public slots:
       void onShowHideButtonThreaded(bool);
       void onButton1Click();


private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

mainwindows.cpp

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

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QObject::connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::onButton1Click);
    QObject::connect(this,&MainWindow::sigShowHide, this, &MainWindow::onShowHideButtonThreaded);
}

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

void MainWindow::onShowHideButtonThreaded(bool a)
{
    qDebug() << " the main thread id = " << QThread::currentThread() << "set the visibility ";
    ui->pushButton_2->setVisible(a);
}

void MainWindow::onButton1Click()
{
    qDebug()<< "clicked";
    qDebug() << " the main thread id = " << QThread::currentThread();
    QThread* l_thread = QThread::create([&]()
    {
        qDebug() << "Running Thread " << QThread::currentThreadId() << " to emit signal only ";
        emit sigShowHide( !this->ui->pushButton_2->isVisible());
    });
    l_thread->start();
  }

`

main.cpp

#include "mainwindow.h"
#include <QApplication>

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

    return a.exec();
}

一个执行的例子是:

the main thread id = QThread(0x116ee18)
Running Thread 0x1ed8 to emit signal only 
the main thread id = QThread(0x116ee18) set the visibility 

如@rustyx 所述:在 Qt 中,与许多其他 GUI 框架一样,GUI 可能仅从主线程更新。

我也被这个问题卡住了,下面是我的两个解决方案:

  1. 使用QMetaObject::invokeMethod

    QMetaObject::invokeMethod(ui->SettingsLabel, "setVisible", Q_ARG(bool, true));
    

    QMetaObject::invokeMethod是线程安全的API,它有一个Qt::ConnectionType type参数,它有一个默认值Qt::AutoConnection.

    一些 Qt::ConnectionType 值的描述:

    Qt::AutoConnection:

    (Default) If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.

    Qt::DirectConnection:

    The slot is invoked immediately when the signal is emitted. The slot is executed in the signalling thread.

    Qt::QueuedConnection:

    The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.

    Qt::BlockingQueuedConnection:

    Same as Qt::QueuedConnection, except that the signalling thread blocks until the slot returns. This connection must not be used if the receiver lives in the signalling thread, or else the application will deadlock.

    从描述中可以看出,API会将你的调用请求放入队列中,然后主线程从队列中取出,最终处理你的调用请求。

    而如果需要获取目标方法的return值,则需要显式将Qt::BlockingQueuedConnection传递给Qt::ConnectionType type参数。因为间接默认值 Qt::QueuedConnection 不会等待调用完成,所以您很可能会得到错误的 return 值。

    但是这个解决方案有一个问题,就是它只支持信号和槽函数,不支持非信号和非槽函数。

  2. 使用connect.

    // In "MainWindow" class declaration
    //
    class MainWindow : public QMainWindow
    {
        // ...
    Q_SIGNALS:
        void setSettingsLabelVisibleSafety(bool value);
        // ...
    };
    
    // In "MainWindow" constructor
    //
    connect(this, &MainWindow::setSettingsLabelVisibleSafety, this,
        [this](bool value) {
            ui->SettingsLabel->setVisible(value);
        }
    );
    
    // In other thread
    //
    setSettingsLabelVisibleSafety(value);
    

    connect还有一个Qt::ConnectionType type参数,如果显式传递receiver参数,则默认值为Qt::AutoConnection,否则默认值为Qt::DirectConnection.因此,您需要显式传递 receiver 参数或显式传递 Qt::AutoConnection 值给 Qt::ConnectionType type 参数。

    如果目标方法具有 return 值,则还需要显式传递 Qt::BlockingQueuedConnection

    此解决方案支持信号和槽函数,以及非信号和非槽函数。

以上两种解决方案都需要您调用qRegisterMetaType来注册用户定义的类型(如果有的话)。

qRegisterMetaType<YourType>("YourType");

显然,我更喜欢第二种解决方案。