2. 线程中的信号槽连接
任务目标
创建一个Qt控制台或Widgets程序,在子线程中通过信号发射数据,主线程接收该信号并更新UI(此处以QLabel文本更新为例)。核心是理解:当信号与槽位于不同线程时,Qt默认使用队列连接(QueuedConnection),信号发射后不会立即执行槽函数,而是投递到接收者所在线程的事件循环中,由事件循环调度执行。
环境与依赖
- Qt 5.12+(Widgets模块)
- 编译器:MSVC / MinGW / GCC 均可
- 项目文件(.pro)需包含
QT += widgets
关键完整代码
// worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include <QThread>
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
public slots:
void doWork(); // 在子线程中执行的入口
signals:
void progressUpdated(int value); // 跨线程信号
void finished();
};
#endif // WORKER_H
// worker.cpp
#include "worker.h"
#include <QThread>
#include <QDebug>
Worker::Worker(QObject *parent) : QObject(parent) {}
void Worker::doWork()
{
// 模拟耗时计算,每200ms发射一次信号
for (int i = 0; i <= 100; i += 20) {
qDebug() << "子线程发射信号,值:" << i
<< ",线程ID:" << QThread::currentThreadId();
emit progressUpdated(i);
QThread::msleep(200);
}
emit finished();
}
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
#include <QPushButton>
#include <QThread>
#include "worker.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void startWork();
void updateLabel(int value);
void onWorkerFinished();
private:
QLabel *m_label;
QPushButton *m_startBtn;
QThread m_workerThread;
Worker *m_worker;
};
#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include <QVBoxLayout>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, m_label(new QLabel("等待开始...", this))
, m_startBtn(new QPushButton("开始工作", this))
, m_worker(nullptr)
{
// 界面布局
auto *central = new QWidget(this);
auto *layout = new QVBoxLayout(central);
layout->addWidget(m_label);
layout->addWidget(m_startBtn);
setCentralWidget(central);
// 创建Worker并移动到子线程
m_worker = new Worker;
m_worker->moveToThread(&m_workerThread);
// 连接信号槽(自动使用QueuedConnection)
connect(m_startBtn, &QPushButton::clicked,
this, &MainWindow::startWork);
connect(this, &MainWindow::startWork,
m_worker, &Worker::doWork);
connect(m_worker, &Worker::progressUpdated,
this, &MainWindow::updateLabel);
connect(m_worker, &Worker::finished,
this, &MainWindow::onWorkerFinished);
// 线程结束时清理
connect(&m_workerThread, &QThread::finished,
m_worker, &QObject::deleteLater);
}
MainWindow::~MainWindow()
{
m_workerThread.quit();
m_workerThread.wait();
}
void MainWindow::startWork()
{
m_startBtn->setEnabled(false);
m_workerThread.start();
}
void MainWindow::updateLabel(int value)
{
qDebug() << "主线程接收信号,值:" << value
<< ",线程ID:" << QThread::currentThreadId();
m_label->setText(QString("当前进度: %1%").arg(value));
}
void MainWindow::onWorkerFinished()
{
m_workerThread.quit();
m_workerThread.wait();
m_startBtn->setEnabled(true);
m_label->setText("工作完成");
}
// main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
运行与验证
- 创建Qt Widgets项目,将上述5个文件(worker.h/cpp、mainwindow.h/cpp、main.cpp)加入项目,并确保.pro文件包含
QT += widgets。 - 编译并运行程序,点击“开始工作”按钮。
- 观察qDebug输出:子线程发射信号时打印的线程ID与主线程接收信号时打印的线程ID不同,证明信号确实跨线程传递。
- 观察UI:QLabel文本依次更新为20%、40%、60%、80%、100%,最后变为“工作完成”。若UI能正常刷新且无崩溃,说明队列连接机制工作正常。
通过判定标准:程序运行无崩溃,UI按预期更新,且控制台输出中发射与接收的线程ID不同。
关键机制说明
当connect时,Qt根据发射者与接收者是否在同一线程自动选择连接方式。本例中m_worker已moveToThread到子线程,而MainWindow在主线程,因此progressUpdated信号与updateLabel槽之间自动使用QueuedConnection——信号发射后立即返回,参数被封装为事件投递到主线程事件队列,由主线程事件循环取出并执行槽函数。这正是跨线程UI更新的安全方式。