本篇博客将深入讨论信号与槽,重点讨论信号与槽的连接方式。信号与槽的连接方式还有什么值得我们注意的地方吗?
之前是如何连接信号与槽的呢?
通过connect函数将指定的信号连接到指定的槽函数上面,接下来将要发生的事情就是信号一旦被发射,相应的槽函数就会被调用。这是我们最直观的认识。
每次调用connect函数时,都省略了这个函数的第5个参数,使我们误以为connect函数就只有4个参数。connect函数其实有5个参数,最后一个参数就是指定信号与槽的连接方式。然而,信号与槽的连接方式也是Qt中多线程编程的难点之一了。
深入信号与槽的连接方式
-Qt::DirectConnection(立即调用)
-Qt::QueuedConnection(异步调用)
-Qt::BlockingQueuedConnection(同步调用)
-Qt::AutoConnection(默认连接)
-Qt::UniqueConnection(单一连接)
小知识
bool connect(const QObject* sender, const char* signal, const QObject* receiver, const char* method, Qt::ConnectType type = Qt::AutoConnection);
信号与槽的连接方式决定槽函数调用时的相关行为。
知识回顾
-每一个线程都有自己的事件队列(一定要熟记,Qt中的每一个线程都有自己的事件队列)
-线程通过事件队列接收信号
-信号在事件循环中被处理
在发射信号的时候,好像并没有指定发射到哪个线程里面去。那么Qt这个平台是如何知道将发射了的信号投递到哪个线程的事件队列中的呢?
依据的就是对象的依附性。看下面的示意图:
在线程1中要发射1个我们自定义的信号,发射之后就进入某个线程的事件队列中去了,那如何决定哪个线程的事件队列呢?
其实就是由对象的依附性决定的,
在发射信号之前,通过connect函数将signal信号连接到obj里面的slot函数上,根据线程的依附性就可以知道,emit signal这条语句执行之后,
signal信号就直接进入到线程2的事件队列,在事件队列中又能干什么呢?什么都干不了。因此就要求线程2开启事件循环,线程2一旦开启事件循环,就会从事件队列中取发射过来的信号了。之后就根据连接调用obj中的slot槽函数了。
信号与槽的连接方式:
1.立即调用
信号与槽的连接方式为立即调用,代码示例如下:
MyObject.h
#ifndef MYOBJECT_H #define MYOBJECT_H #include <QObject> class MyObject : public QObject { Q_OBJECT public: explicit MyObject(QObject* parent = 0); signals: protected slots: void testSlot(); }; #endif // MYOBJECT_H
#ifndef MYOBJECT_H #define MYOBJECT_H #include <QObject> class MyObject : public QObject { Q_OBJECT public: explicit MyObject(QObject* parent = 0); signals: protected slots: void testSlot(); }; #endif // MYOBJECT_H
TestThread.h
#ifndef TESTTHREAD_H #define TESTTHREAD_H #include <QThread> class TestThread : public QThread { Q_OBJECT protected: void run(); public: explicit TestThread(); signals: void testSignal(); }; #endif // TESTTHREAD_H
#ifndef TESTTHREAD_H #define TESTTHREAD_H #include <QThread> class TestThread : public QThread { Q_OBJECT protected: void run(); public: explicit TestThread(); signals: void testSignal(); }; #endif // TESTTHREAD_H
MyObject.cpp
#include "MyObject.h" #include <QThread> #include <QDebug> MyObject::MyObject(QObject* parent) : QObject(parent) { } void MyObject :: testSlot() { qDebug() << "void MyObject :: testSlot() tid = " << QThread::currentThreadId(); }
#include "MyObject.h" #include <QThread> #include <QDebug> MyObject::MyObject(QObject* parent) : QObject(parent) { } void MyObject :: testSlot() { qDebug() << "void MyObject :: testSlot() tid = " << QThread::currentThreadId(); }
TestThread.cpp
#include "TestThread.h" #include <QDebug> TestThread::TestThread() { } void TestThread::run() { qDebug() << "void TestThread::run() -- begin tid = " << currentThreadId(); for(int i=0; i<3; i++) { qDebug() << "void TestThread::run() i = " << i; sleep(1); } emit testSignal(); qDebug() << "void TestThread::run() -- end" ; }
#include "TestThread.h" #include <QDebug> TestThread::TestThread() { } void TestThread::run() { qDebug() << "void TestThread::run() -- begin tid = " << currentThreadId(); for(int i=0; i<3; i++) { qDebug() << "void TestThread::run() i = " << i; sleep(1); } emit testSignal(); qDebug() << "void TestThread::run() -- end" ; }
main.cpp
#include <QCoreApplication> #include <QDebug> #include <QThread> #include "MyObject.h" #include "TestThread.h" void direct_connection() { static TestThread t; static MyObject m; QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot()),Qt::DirectConnection); t.start(); //启动线程 t.wait(5*1000); //等待5s t.quit(); //结束t中的线程循环 } int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); qDebug() << "main tid = " << QThread::currentThreadId(); direct_connection(); return a.exec(); }
#include <QCoreApplication> #include <QDebug> #include <QThread> #include "MyObject.h" #include "TestThread.h" void direct_connection() { static TestThread t; static MyObject m; QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot()),Qt::DirectConnection); t.start(); //启动线程 t.wait(5*1000); //等待5s t.quit(); //结束t中的线程循环 } int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); qDebug() << "main tid = " << QThread::currentThreadId(); direct_connection(); return a.exec(); }
运行结果如下:
根据运行结果进行深入剖析:
通过打印的语句进行分析,主线程的id是0xc0c,由于调用了direct_connect,所以启动了一个子线程,子线程的id是0x1784,接线来延续3s中后发射信号,发射信号后槽函数必然会被调用。调用的槽函数在哪个线程中被执行的呢?通过打印的线程id可知,它是在子线程中被执行的。
此时你有没有感到奇怪,对象m依附于主线程,根据之前说过的,槽函数显然在主线程中被调用执行,但是我们指定了信号与槽的连接方式为直接调用。
导致的行为就是在发射信号的地方直接调用了槽函数,换句话说emit testSignal()就直接替换成了槽函数的调用语句testSlot();
2.异步调用
main.cpp
#include <QCoreApplication> #include <QDebug> #include <QThread> #include "MyObject.h" #include "TestThread.h" void queued_connection() { static TestThread t; static MyObject m; QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot()),Qt::QueuedConnection); t.start(); //启动线程 t.wait(5*1000); //等待5s t.quit(); //结束t中的线程循环 } int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); qDebug() << "main tid = " << QThread::currentThreadId(); queued_connection(); return a.exec(); }
#include <QCoreApplication> #include <QDebug> #include <QThread> #include "MyObject.h" #include "TestThread.h" void queued_connection() { static TestThread t; static MyObject m; QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot()),Qt::QueuedConnection); t.start(); //启动线程 t.wait(5*1000); //等待5s t.quit(); //结束t中的线程循环 } int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); qDebug() << "main tid = " << QThread::currentThreadId(); queued_connection(); return a.exec(); }
根据运行结果进行深入剖析:
主线程id是0x754,子线程的id是0xd74,子线程先延迟3s中,然后再发射信号。从运行结果看,槽函数并没有被立即调用,没有在子线程中调用槽函数,那么槽函数究竟是在哪调用的呢?
显然是在主线程中被调用的,那么为什么会在主线程中被调用呢?
根据对象的依附性可以知道,m对象依附于主线程,发射的信号就进入了主线程的事件循环队列中,又因为这个地方的连接是QueuedConnection,所以说进入到主线程的事件循环队列中后,子线程就不管了,接着就向下执行。什么时候调用槽函数是由主线程决定的。
3.同步调用
#include <QCoreApplication> #include <QDebug> #include <QThread> #include "MyObject.h" #include "TestThread.h" void blocking_queued_connection() { static TestThread t; static MyObject m; QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot()),Qt::BlockingQueuedConnection); t.start(); //启动线程 t.wait(5*1000); //等待5s t.quit(); //结束t中的线程循环 } int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); qDebug() << "main tid = " << QThread::currentThreadId(); blocking_queued_connection(); return a.exec(); }
4.默认连接
5.单一连接
小结: