Qt 信号与槽的 5 种连接方式

Qt 信号与槽的 5 种连接方式

Qt 信号与槽提供了 5 种连接方式(Connection Type),用于控制信号发射时槽函数的调用行为。

一、连接方式枚举

cpp 复制代码
enum ConnectionType {
    AutoConnection,           // 自动连接(默认)
    DirectConnection,         // 直接连接
    QueuedConnection,         // 队列连接
    BlockingQueuedConnection, // 阻塞队列连接
    UniqueConnection          // 唯一连接(标志位,与其他组合使用)
};

二、详细说明

1. AutoConnection(自动连接)

默认方式,根据信号发送者和接收者是否在同一线程自动选择连接类型。

cpp 复制代码
connect(sender, &Sender::signal, receiver, &Receiver::slot);
// 等价于
connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::AutoConnection);

行为规则:

  • 在同一线程:使用 DirectConnection
  • 在不同线程:使用 QueuedConnection

示例:

cpp 复制代码
class Worker : public QObject {
    Q_OBJECT
public slots:
    void doWork() {
        qDebug() << "Worker thread:" << QThread::currentThread();
        emit result(42);
    }
signals:
    void result(int value);
};

class Controller : public QObject {
    Q_OBJECT
public slots:
    void handleResult(int value) {
        qDebug() << "Controller thread:" << QThread::currentThread();
        qDebug() << "Got result:" << value;
    }
};

// 使用场景
Worker *worker = new Worker;
Controller *controller = new Controller;

// 1. 同一线程:直接连接
connect(worker, &Worker::result, controller, &Controller::handleResult);
worker->doWork();  // 槽函数在 worker 线程执行

// 2. 不同线程:队列连接
QThread *thread = new QThread;
worker->moveToThread(thread);
connect(worker, &Worker::result, controller, &Controller::handleResult);
thread->start();
worker->doWork();  // 槽函数在 controller 线程执行(主线程)

输出示例:

plain 复制代码
// 同一线程
Worker thread: QThread(0x1234)
Controller thread: QThread(0x1234)

// 不同线程
Worker thread: QThread(0x5678)
Controller thread: QThread(0x1234)  // 主线程

2. DirectConnection(直接连接)

槽函数在信号发射的线程中立即执行,同步调用。

cpp 复制代码
connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::DirectConnection);

特点:

  • 槽函数在信号发送者的线程中执行
  • 同步调用,槽函数返回后才继续执行信号后面的代码
  • 类似于直接函数调用

示例:

cpp 复制代码
class Emitter : public QObject {
    Q_OBJECT
public:
    void emitSignal() {
        qDebug() << "Before emit - thread:" << QThread::currentThread();
        emit mySignal();
        qDebug() << "After emit - thread:" << QThread::currentThread();
    }
signals:
    void mySignal();
};

class Receiver : public QObject {
    Q_OBJECT
public slots:
    void onSignal() {
        qDebug() << "In slot - thread:" << QThread::currentThread();
        QThread::sleep(2);  // 模拟耗时操作
        qDebug() << "Slot finished";
    }
};

// 使用
Emitter emitter;
Receiver receiver;
connect(&emitter, &Emitter::mySignal, &receiver, &Receiver::onSignal, 
        Qt::DirectConnection);

emitter.emitSignal();

输出:

plain 复制代码
Before emit - thread: QThread(0x1234)
In slot - thread: QThread(0x1234)
Slot finished
After emit - thread: QThread(0x1234)   // 等待槽函数完成后才输出

注意事项:

  • 槽函数执行时间过长会阻塞信号发送者线程
  • 如果槽函数和发送者在不同线程,可能导致线程安全问题
  • 危险:在不同线程中使用 DirectConnection 可能导致崩溃

3. QueuedConnection(队列连接)

槽函数在接收者所在线程的事件循环中异步执行。

cpp 复制代码
connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::QueuedConnection);

特点:

  • 信号发射后立即返回,不等待槽函数执行
  • 槽函数在接收者线程的事件循环中排队执行
  • 参数会被复制(需要元类型支持)

示例:

cpp 复制代码
class Emitter : public QObject {
    Q_OBJECT
public:
    void emitSignal() {
        qDebug() << "Before emit - thread:" << QThread::currentThread();
        emit mySignal();
        qDebug() << "After emit - thread:" << QThread::currentThread();
    }
signals:
    void mySignal();
};

class Receiver : public QObject {
    Q_OBJECT
public slots:
    void onSignal() {
        qDebug() << "In slot - thread:" << QThread::currentThread();
        QThread::sleep(2);
        qDebug() << "Slot finished";
    }
};

// 使用
Emitter emitter;
Receiver receiver;

// 将 receiver 移到另一个线程
QThread thread;
receiver.moveToThread(&thread);
thread.start();

connect(&emitter, &Emitter::mySignal, &receiver, &Receiver::onSignal, 
        Qt::QueuedConnection);

emitter.emitSignal();
qDebug() << "After emit in main thread";

输出:

plain 复制代码
Before emit - thread: QThread(0x1234)  // 主线程
After emit - thread: QThread(0x1234)   // 立即返回
After emit in main thread              // 立即输出
In slot - thread: QThread(0x5678)      // 在 receiver 线程执行
Slot finished                          // 2 秒后输出

适用场景:

  • 跨线程通信(最常用)
  • 不需要立即响应的操作
  • 避免阻塞信号发送者

4. BlockingQueuedConnection(阻塞队列连接)

与 QueuedConnection 类似,但信号发送者会阻塞等待槽函数执行完成。

cpp 复制代码
connect(sender, &Sender::signal, receiver, &Receiver::slot, 
        Qt::BlockingQueuedConnection);

特点:

  • 槽函数在接收者线程的事件循环中执行
  • 信号发送者线程阻塞直到槽函数返回
  • 不能在同一线程中使用(会导致死锁)

示例:

cpp 复制代码
class Emitter : public QObject {
    Q_OBJECT
public:
    void emitSignal() {
        qDebug() << "Before emit - thread:" << QThread::currentThread();
        emit mySignal();
        qDebug() << "After emit - thread:" << QThread::currentThread();
    }
signals:
    void mySignal();
};

class Receiver : public QObject {
    Q_OBJECT
public slots:
    void onSignal() {
        qDebug() << "In slot - thread:" << QThread::currentThread();
        QThread::sleep(2);
        qDebug() << "Slot finished";
    }
};

// 使用
Emitter emitter;
Receiver receiver;

// 将 receiver 移到另一个线程
QThread thread;
receiver.moveToThread(&thread);
thread.start();

// 使用阻塞连接
connect(&emitter, &Emitter::mySignal, &receiver, &Receiver::onSignal, 
        Qt::BlockingQueuedConnection);

qDebug() << "Main thread before emit";
emitter.emitSignal();  // 这里会阻塞等待槽函数完成
qDebug() << "Main thread after emit";

输出:

plain 复制代码
Main thread before emit
Before emit - thread: QThread(0x1234)  // 主线程
In slot - thread: QThread(0x5678)      // receiver 线程
Slot finished                          // 2 秒后
After emit - thread: QThread(0x1234)   // 阻塞完成后才返回
Main thread after emit

适用场景:

  • 需要等待跨线程操作完成(如获取返回值)
  • 需要同步结果的跨线程调用

注意: 同一线程中使用会导致死锁!

cpp 复制代码
// ❌ 错误:同一线程使用 BlockingQueuedConnection 会死锁
Emitter emitter;
Receiver receiver;
connect(&emitter, &Emitter::mySignal, &receiver, &Receiver::onSignal,
        Qt::BlockingQueuedConnection);
emitter.emitSignal();  // 死锁!信号发送者和接收者在同一线程

5. UniqueConnection(唯一连接)

这是一个标志位,与其他类型组合使用,确保同一个信号-槽对只连接一次。

cpp 复制代码
// 避免重复连接
connect(sender, &Sender::signal, receiver, &Receiver::slot, 
        Qt::UniqueConnection);  // 自动与 AutoConnection 组合

// 组合使用
connect(sender, &Sender::signal, receiver, &Receiver::slot,
        Qt::QueuedConnection | Qt::UniqueConnection);

特点:

  • 防止同一个信号连接到同一个对象的同一个槽多次
  • 如果连接已存在,connect 返回 false
  • 通常与其他连接类型组合使用

示例:

cpp 复制代码
class Emitter : public QObject {
    Q_OBJECT
signals:
    void mySignal();
};

class Receiver : public QObject {
    Q_OBJECT
public slots:
    void onSignal() {
        static int count = 0;
        qDebug() << "Slot called:" << ++count;
    }
};

// 使用
Emitter emitter;
Receiver receiver;

// 第一次连接
bool success = connect(&emitter, &Emitter::mySignal, 
                       &receiver, &Receiver::onSignal,
                       Qt::AutoConnection | Qt::UniqueConnection);
qDebug() << "First connect:" << success;  // true

// 第二次连接相同信号槽
success = connect(&emitter, &Emitter::mySignal, 
                  &receiver, &Receiver::onSignal,
                  Qt::AutoConnection | Qt::UniqueConnection);
qDebug() << "Second connect:" << success;  // false

// 发射信号
emit emitter.mySignal();  // 槽函数只调用一次

输出:

plain 复制代码
First connect: true
Second connect: false
Slot called: 1

三、连接方式对比表

连接类型 执行线程 是否阻塞 跨线程安全 参数传递 使用场景
AutoConnection 自动选择 看情况 ✅ 自动处理 默认选择
DirectConnection 发送者线程 ✅ 阻塞 ❌ 危险 引用传递 同线程高性能调用
QueuedConnection 接收者线程 ❌ 非阻塞 ✅ 值传递(需复制) 跨线程通信
BlockingQueuedConnection 接收者线程 ✅ 阻塞 ✅ 值传递(需复制) 等待跨线程结果
UniqueConnection 标志位 N/A N/A N/A 防止重复连接

四、实际应用示例

完整的跨线程通信示例

cpp 复制代码
// 工作线程类
class Worker : public QObject
{
    Q_OBJECT
    
public:
    Worker() {
        qDebug() << "Worker created in thread:" << QThread::currentThread();
    }
    
public slots:
    void startWork() {
        qDebug() << "Worker working in thread:" << QThread::currentThread();
        
        for (int i = 1; i <= 5; ++i) {
            emit progress(i * 20);
            QThread::sleep(1);
        }
        
        emit finished("Work completed successfully!");
    }
    
signals:
    void progress(int percent);
    void finished(const QString& result);
};

// 控制器类(在主线程)
class Controller : public QObject
{
    Q_OBJECT
    
public:
    Controller() {
        qDebug() << "Controller created in thread:" << QThread::currentThread();
        
        // 创建工作对象和线程
        m_worker = new Worker;
        m_thread = new QThread(this);
        m_worker->moveToThread(m_thread);
        
        // 跨线程连接(自动使用 QueuedConnection)
        connect(m_thread, &QThread::started, 
                m_worker, &Worker::startWork);
        
        connect(m_worker, &Worker::progress,
                this, &Controller::onProgress);
        
        // 等待工作完成(使用 BlockingQueuedConnection)
        connect(m_worker, &Worker::finished,
                this, &Controller::onFinished);
                
        // 工作完成后的清理(使用 DirectConnection 同线程)
        connect(this, &Controller::cleanup,
                this, &Controller::doCleanup,
                Qt::DirectConnection);
    }
    
    void start() {
        qDebug() << "Starting worker thread...";
        m_thread->start();
    }
    
public slots:
    void onProgress(int percent) {
        qDebug() << "Progress in thread:" << QThread::currentThread();
        qDebug() << "Progress:" << percent << "%";
        
        if (percent == 100) {
            // 演示 BlockingQueuedConnection
            QString result;
            QMetaObject::invokeMethod(m_worker, "getResult",
                                      Qt::BlockingQueuedConnection,
                                      Q_RETURN_ARG(QString, result));
            qDebug() << "Got result with blocking:" << result;
        }
    }
    
    void onFinished(const QString& result) {
        qDebug() << "Finished in thread:" << QThread::currentThread();
        qDebug() << "Result:" << result;
        
        // 清理工作
        emit cleanup();
    }
    
    void doCleanup() {
        qDebug() << "Cleanup in thread:" << QThread::currentThread();
        m_thread->quit();
        m_thread->wait();
        delete m_worker;
    }
    
signals:
    void cleanup();
    
private:
    Worker *m_worker;
    QThread *m_thread;
};

// 使用
int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);
    
    qDebug() << "Main thread:" << QThread::currentThread();
    
    Controller controller;
    controller.start();
    
    return app.exec();
}

五、选择指南

cpp 复制代码
// 1. 同线程,需要高性能,不关心阻塞
connect(a, &A::signal, b, &B::slot, Qt::DirectConnection);

// 2. 跨线程通信(最常用)
connect(a, &A::signal, b, &B::slot, Qt::QueuedConnection);

// 3. 需要等待跨线程结果
connect(a, &A::signal, b, &B::slot, Qt::BlockingQueuedConnection);

// 4. 防止重复连接
connect(a, &A::signal, b, &B::slot, Qt::UniqueConnection);

// 5. 默认情况,让 Qt 自动选择
connect(a, &A::signal, b, &B::slot);  // AutoConnection

六、常见错误与注意事项

cpp 复制代码
// ❌ 错误1:跨线程使用 DirectConnection
// 可能导致崩溃或未定义行为
QThread thread;
MyObject obj;
obj.moveToThread(&thread);
connect(&obj, &MyObject::signal, &receiver, &Receiver::slot, 
        Qt::DirectConnection);  // 危险!

// ❌ 错误2:同一线程使用 BlockingQueuedConnection
connect(&obj, &MyObject::signal, &receiver, &Receiver::slot,
        Qt::BlockingQueuedConnection);  // 死锁!

// ❌ 错误3:未注册的自定义类型用于队列连接
struct MyData { int value; };  // 未注册
connect(this, &MyClass::sendData, receiver, &Receiver::processData);
// 运行时会报错:QObject::connect: Cannot queue arguments of type 'MyData'

// ✅ 正确:注册自定义类型
qRegisterMetaType<MyData>("MyData");

// ✅ 正确:多线程通信模式
// 发送者线程 → 队列连接 → 接收者线程

七、总结

连接方式 记忆口诀 核心特征
AutoConnection "自动选择" 默认方式,智能选择
DirectConnection "同步调用" 立即执行,阻塞发送者
QueuedConnection "异步排队" 延迟执行,不阻塞发送者
BlockingQueuedConnection "同步跨线程" 跨线程阻塞等待结果
UniqueConnection "唯一连接" 防止重复连接
场景 选择
跨线程同步阻塞等待结果 ✅ BlockingQueuedConnection
跨线程异步执行不阻塞 ✅ QueuedConnection
同线程异步执行不阻塞 ✅ QueuedConnection
同线程阻塞等待结果 ✅ DirectConnection
跨线程阻塞等待结果 ✅ BlockingQueuedConnection
防止重复连接 ✅ UniqueConnection

最佳实践:

  1. 默认使用 AutoConnection,让 Qt 自动选择
  2. 跨线程通信使用 QueuedConnection
  3. 需要返回值使用 BlockingQueuedConnection
  4. 同线程高性能调用可使用 DirectConnection
  5. 防止重复连接添加 UniqueConnection 标志
  6. 自定义类型用于队列连接时务必注册元类型
相关推荐
qq_401700414 小时前
Qt Signal Mapper:一堆按钮,怎么共用一个槽函数
开发语言·qt
Python图像识别4 小时前
35-【2027毕设】YOLO11中草药检测识别系统 - Python完整源码+PyQt5界面+训练模型+数据集
python·qt·课程设计
Lhan.zzZ5 小时前
Qt/QML:业务记录单展示、签名确认与 PDF 导出开发
c++·qt·pdf
Quz15 小时前
QML 动画选择:GIF与Lottie 的区别,以及 Qt 6.8 与 6.11 的 Lottie 实现差异
qt
时间的拾荒人19 小时前
Qt 界面美化实战:QSS 样式表
开发语言·qt·面试
qq_4017004120 小时前
Qt 串口/网口通信:Hex 与 ASCII 编码转换深度指南
开发语言·数据库·qt
Python图像识别1 天前
30-【2027毕设】YOLO11管道缺陷检测识别系统 - Python完整源码+PyQt5界面+训练模型+数据集
python·qt·课程设计
道尔柯南1 天前
【QT】从零认识 Qt:跨平台 C++ GUI 开发入门
开发语言·c++·qt
MayZork1 天前
std::filesystem 文件操作详解
开发语言·c++·qt
kaixin_learn_qt_ing1 天前
[银河麒麟]因为没有安装串口相关驱动,导致无法检测到对应的串口,后来安装后,我的qt的相关接口就可以识别到了
qt