[英]Qt GUI doesn't work with std::thread as I expect
我的項目的核心是獨立於GUI框架,這就是為什么我更喜歡std :: thread。 但是當線程使用時Qt給我一個錯誤。
劣勢停止是因為它收到了來自操作系統的信號。
信號名稱:SIGSEGV
信號含義:分段故障
//MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <thread>
#include <mutex>
#include <QMainWindow>
namespace Ui { class MainWindow; }
struct Observer
{
virtual void notify() = 0;
};
class Core
{
public:
std::thread *run()
{
std::thread thread(&Core::runP, this);
thread.detach();
return &thread;
}
void setObserver(Observer *observer) { _observer = observer; }
int ii() const { return _ii; }
void nextIi() { _ii++; }
void lock() { _mutex.lock(); }
bool tryLock() { return _mutex.try_lock(); }
void unlock() { _mutex.unlock(); }
private:
void runP()
{
for (int i = 1; i <= 1000; i++) {
if (i % 10 == 0) {
lock();
nextIi();
unlock();
notify();
}
}
}
void notify() { _observer->notify(); } //!!!
Observer *_observer;
int _ii;
std::mutex _mutex;
};
struct MwObserver : public Observer
{
explicit MwObserver(struct MainWindow *mainWindow) { _mainWindow = mainWindow; }
virtual void notify();
MainWindow *_mainWindow;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow() { delete _ui; }
void upd();
public slots:
void run() { _core.run(); }
private:
Ui::MainWindow *_ui;
MwObserver _observer;
Core _core;
};
inline void MwObserver::notify() { _mainWindow->upd(); }
#endif
-
//MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
_ui(new Ui::MainWindow),
_observer(this)
{
_ui->setupUi(this);
connect(_ui->pushButtonRun, SIGNAL(clicked(bool)), this, SLOT(run()));
}
void MainWindow::upd()
{
_core.lock();
setWindowTitle(QString::number(_core.ii()));
_core.unlock();
}
這里存在多個問題,首先是perencia已經注意到的最明顯的問題。 您正在返回指向堆棧變量的指針。 用c ++術語來說,這是不可接受的。
其次。 崩潰來自於不使用std::thread
,而是來自競爭條件。 Qt事件循環不知道你的互斥,所以你的setWindowTitle
調用引入了一場比賽,導致崩潰。 您需要使用QMetaObject :: invokeMethod將函數發布到Qts事件循環。
示例:更改
inline void MwObserver::notify() { _mainWindow->upd(); }
至
inline void MwObserver::notify() {
if(!QMetaObject::invokeMethod(_mainWindow, "upd", Qt::QueuedConnection))
std::cerr << " Failed to invoke method" << std::endl;
}
額外的包括可能適用
這將從不同於GUI線程的線程更新GUI! 這是不允許的。 為什么不使用QThread和信號/插槽機制來更新窗口標題。 Qt框架自動執行線程切換。
class Core : public QObject
{
Q_OBJECT
public:
explicit Core(QObject * parent = 0) : QObject(parent) {}
signals:
void notify();
public slots:
void nextIi() { _ii++; }
void runP()
{
for (int i = 1; i <= 1000; i++) {
if (i % 10 == 0) {
nextIi();
notify();
}
}
}
private:
Q_DISABLE_COPY(Core);
int _ii;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void run() {_th.start();}
void upd(int ii) {setWindowTitle(QString::number(ii));}
private:
Ui::MainWindow *_ui;
Core _core;
QThread _th;
};
//MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
_ui(new Ui::MainWindow),
_observer(this)
{
_ui->setupUi(this);
connect(_ui->pushButtonRun, SIGNAL(clicked(bool)), this, SLOT(run()));
connect(&_core, SIGNAL(notify(int)), this, SLOT(upd(int)));
_core.moveToThread(&_th);
}
MainWindow::~MainWindow()
{
delete _ui;
_th.quit();
_th.wait(1000);
}
您正在堆棧上創建線程並返回指向該線程的指針。 在run()
,指針不再有效。
除了返回指向堆棧變量的指針以及從QT未知的線程對象更新GUI。 我沒有從你的代碼中看到你在哪里設置了Core
類的_observer
成員。 MainWindow
類的_core
成員沒有setObserver
調用。
因此, MainWindow
類的_core
調用_core
成員的_core
,但之后_core._observer
包含垃圾。 我認為這是在調用Core
類的notify
方法時出現Segmentaion Fault
的原因。
我已經給出了所有問題的答案,讓我總結一下。
程序崩潰無關和穿線,問題是, _observer
在_core
成員MainWindow
沒有設置。 必須添加對setObserver的調用。
explicit MainWindow( QWidget *parent = nullptr ) :
QMainWindow( parent ),
_observer( this )
{
_core.setObserver( &_observer );
}
這將導致下一個問題,即觀察者實際上從另一個線程調用udp
消息,導致在不同的線程上下文中進行UI更新。 要解決這個問題,最簡單的方法是使用Qt的Qt::QueuedConnection
。 要啟用此功能,我們必須使upt()
成為一個插槽。
public slots:
void run();
void upd();
然后我們可以使用QMetaObject::invokeMethod
來調用它
inline void MwObserver::notify()
{
QMetaObject::invokeMethod( _mainWindow, "upd", Qt::QueuedConnection );
}
或者通過從QObject
導出MwObserver
,給它一個信號,並將該信號連接到upd
槽並在notify
提升信號來使用信號/槽連接。
struct MwObserver
: public QObject
, public Observer
{
Q_OBJECT;
signals:
void sigUpd();
public:
explicit MwObserver( MainWindow *mainWindow );
virtual void notify()
MainWindow *_mainWindow;
};
void MwObserver::notify()
{
sigUpd();
}
MwObserver::MwObserver( MainWindow *mainWindow )
{
_mainWindow = mainWindow;
connect( this, SIGNAL(sigUpd()), _mainWindow, SLOT(upd()) )
}
免責聲明:我有一段時間沒有使用過Qt,但是在Linux / UNIX上使用X / XMotif,GUI必須在“主線程”中運行,而不是在生成的線程中運行。 也許這適用於您的情況。 只是想一想,讓你的GUI代碼在主線程中運行。
最好的方法是使用QObejct實例包裝純C ++代碼,並在此對象從純C ++代碼接收一些通知時觸發信號。
在你的情況下:
class MwObserver : public QObject, public Observer
{
Q_OBJECT
public:
explicit MwObserver(QObject *parent)
: QObject(parent)
{}
signals:
void SomeEvent();
protected:
// Observer
void notify() {
emit SomeEvent();
}
};
現在MainWindow應該以這種方式將一些插槽連接到信號,並且一切都應該開箱即用(Qt將在幕后跳線)。
在您的代碼表單注釋中 ,崩潰是由無效使用臨時對象引起的。 這是INVALID C ++代碼無論返回什么樣的對象:
std::thread *run()
{
std::thread thread(&Core::runP, this);
thread.detach();
return &thread;
}
您無法返回指向函數方法的本地對象的指針,因為當您返回函數時此對象立即變為無效。 這是基本的C ++知識。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.