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. 自定义类型用于队列连接时务必注册元类型
相关推荐
曾几何时`2 小时前
QT——对象树
开发语言·qt
汪宁宇2 小时前
(C++) Qt5.15.12 + GDAL库 等高线生成示例代码
c++·qt·等高线·gdal·等值线·rec533
我在人间贩卖青春3 小时前
Qt 元对象系统(MOC)
qt·moc·元对象
特立独行的猫a3 小时前
HarmonyOS鸿蒙PC开源QT软件移植:移植开源文本编辑器 NotePad--(Ndd)到鸿蒙 PC实践总结
qt·开源·notepad++·harmonyos·notepad--·鸿蒙pc
Acnidouwo4 小时前
QT程序的dpi导致显示异常处理方法
开发语言·qt
咸鱼翻身小阿橙4 小时前
Qt P5
开发语言·数据库·qt
小小码农Come on16 小时前
WorkerScript处理qml多线程处理异步数据
qt
小灰灰搞电子17 小时前
Qt 中的队列解析
qt
原来是猿1 天前
QT初识【创建项目+对象树】
开发语言·qt