最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談Qt信號(hào)槽與事件循環(huán)的關(guān)系

 更新時(shí)間:2022年08月10日 09:57:26   作者:KumaNPC  
本文主要介紹了Qt信號(hào)槽與事件循環(huán)的關(guān)系,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

關(guān)于信號(hào)槽與事件循環(huán),相關(guān)的文章非常多了,本文不做過(guò)多介紹。本文主要是通過(guò)簡(jiǎn)單的幾個(gè)例子,嘗試解釋信號(hào)槽與事件循環(huán)的關(guān)系,幫助進(jìn)一步理解。

一、信號(hào)槽

類中聲明的信號(hào),實(shí)際也是聲明一個(gè)函數(shù),其實(shí)現(xiàn)由moc機(jī)制自動(dòng)生成在moc文件里,信號(hào)觸發(fā)意味著函數(shù)調(diào)用:

// widget.h , Widget類
signals:
    void widgetSignal1();
// moc_widget.cpp
void Widget::widgetSignal1()
{
    QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}

Qt中通過(guò)QObject::connect建立起信號(hào)與信號(hào)或槽之間的連接,信號(hào)觸發(fā)(也即函數(shù)調(diào)用)時(shí),查找連接信息,從而觸發(fā)槽的調(diào)用。

QObject::connect,參數(shù)可以指定連接類型(Qt::ConnectionType),可以確定槽以什么樣的方式執(zhí)行。常用自動(dòng)連接、直接連接、隊(duì)列連接。自動(dòng)連接信號(hào)觸發(fā)時(shí),根據(jù)當(dāng)前線程與接收者(receiver)所在線程是否相同,選擇直接連接或者隊(duì)列連接的執(zhí)行邏輯。

二、事件循環(huán)

很多GUI框架都有事件循環(huán)這個(gè)概念,借由事件隊(duì)列來(lái)驅(qū)動(dòng)程序執(zhí)行不同的邏輯。簡(jiǎn)單理解就是,線程內(nèi)維護(hù)一個(gè)事件隊(duì)列,當(dāng)事件隊(duì)列為空時(shí),線程等待新的事件到來(lái)。有事件時(shí),線程取出一個(gè)事件,調(diào)用該事件對(duì)應(yīng)的處理過(guò)程。

UI線程(主線程),通常事件會(huì)比較多,例如鼠標(biāo)鍵盤輸出、重繪等。自定義的線程(QThread實(shí)例),也可以啟動(dòng)一個(gè)屬于自己的事件循環(huán),事件多數(shù)由程序自己產(chǎn)生。

而Qt的信號(hào)槽的機(jī)制,一部分也是依賴事件循環(huán)實(shí)現(xiàn)跨線程執(zhí)行槽。

三、關(guān)系

盡管常說(shuō)Qt的信號(hào)槽依賴事件循環(huán),但實(shí)際運(yùn)用起來(lái),總是出現(xiàn)各種各樣的問(wèn)題。這里寫幾個(gè)使用例子,幫助總結(jié)一下。

1. 基本寫法

先做個(gè)簡(jiǎn)單的測(cè)試,在當(dāng)前線程創(chuàng)建對(duì)象并觸發(fā)信號(hào):

TestObject * object = new TestObject();
connect(this, SIGNAL(widgetSignal1()), object, SLOT(doTest1()));

qDebug() << "emit in thread: " << QThread::currentThreadId();
emit widgetSignal1();
qDebug() << timer.elapsed();
void TestObject::doTest1()
{
    qDebug() << "doTest1 in thread: " << QThread::currentThreadId();
    QThread::currentThread()->msleep(1000);
}

此時(shí)輸出:

emit in thread:  0x3bd0
doTest1 in thread:  0x3bd0
1000

如果將connect改為隊(duì)列連接:

emit in thread:  0x1fe0
0
doTest1 in thread:  0x1fe0

至少可以看出,信號(hào)的觸發(fā)時(shí)的線程與槽執(zhí)行線程一致,并且默認(rèn)連接時(shí),似乎等槽執(zhí)行完成后,才執(zhí)行后面的代碼。而強(qiáng)制使用隊(duì)列隊(duì)列連接時(shí),槽的執(zhí)行被延遲,如果深入研究的話,會(huì)發(fā)現(xiàn)此時(shí)Qt生成了一個(gè)QMetaCallEvent事件,事件循環(huán)參與其中。

2. 加入額外的線程

這里接涉及不同方式的影響,1. 繼承QThread重寫QThread::run不啟動(dòng)事件循環(huán);2. moveToThread使用默認(rèn)事件循環(huán);3. QtConcurrent線程接口和std::thread開(kāi)啟線程;4.信號(hào)觸發(fā)者和接收者創(chuàng)建時(shí)機(jī); 5.信號(hào)觸發(fā)時(shí)的線程。這幾種情況又相互交錯(cuò),非常復(fù)雜。

(下面的測(cè)試代碼不釋放對(duì)象,不考慮內(nèi)存泄漏,如果某些測(cè)試與預(yù)期不符,可能是信號(hào)多次連接的問(wèn)題)

繼承QThread,并重寫QThread::run

這是初學(xué)者最常用的一種寫法,QThread子類定義信號(hào)或者槽,run內(nèi)觸發(fā)信號(hào)。此時(shí)就涉及到一個(gè)非常重要的知識(shí)點(diǎn):對(duì)象的所在線程是創(chuàng)建該對(duì)象時(shí)線程,這也意味著,盡管QThread::run方法是在線程中執(zhí)行,但QThread對(duì)象仍舊是屬于創(chuàng)建它的線程:

MyThread * thread = new MyThread();    // MyThread繼承自QThread
thread->start();
connect(this,SIGNAL(widgetSignal1()), thread, SLOT(doThreadSlot()));
qDebug() << "emit in thread: " << QThread::currentThreadId();
emit widgetSignal1();
qDebug() << timer.elapsed();

輸出:

emit in thread:  0x52c
doThreadSlot in thread:  0x52c
2000

此時(shí),觸發(fā)的時(shí)直接連接的邏輯,輸出跟上面基本寫法里一樣。也可以調(diào)用QObject::thread,看看線程id是否與創(chuàng)建時(shí)的線程一致。

如果重寫QThread::run方法,在run內(nèi)觸發(fā)MyThread信號(hào):

// Widget類
void Widget::on_pushButton_clicked()
{
    MyThread * thread = new MyThread();
    connect(thread,SIGNAL(progressChanged()), this, SLOT(onProcessChanged()));
    thread->start();
}
// MyThread類
void MyThread::run()
{
    qDebug() << "emit in thread: " << QThread::currentThreadId();
    emit progressChanged();
}

測(cè)試輸出,線程不一致。

QThread::run的默認(rèn)實(shí)現(xiàn)時(shí)啟動(dòng)一個(gè)事件循環(huán),上面的重寫沒(méi)有啟動(dòng)事件循環(huán)。這里就出現(xiàn)了第二個(gè)關(guān)鍵點(diǎn):為什么沒(méi)有事件循環(huán),信號(hào)還是正常觸發(fā)了? 當(dāng)然你可能會(huì)懷疑,也許Qt背后偷偷啟動(dòng)了個(gè)呢。

QtConcurrent線程接口和std::thread試試

TestObject * object = new TestObject();
connect(this, SIGNAL(widgetSignal1()), object, SLOT(doTest1()));
QtConcurrent::run([this](){
    qDebug() << "emit in thread: " << QThread::currentThreadId();
    emit widgetSignal1();
});

輸出:

emit in thread:  0x3088
0
doTest1 in thread:  0x2ac0

槽正常執(zhí)行,并且使用了隊(duì)列觸發(fā),將QtConcurrent換成std::thread后,也是同樣的結(jié)果。因此,信號(hào)觸發(fā)時(shí),是不需要當(dāng)前線程有事件循環(huán),因?yàn)槭峭ㄟ^(guò)查找連接信息并根據(jù)接收者所在線程來(lái)確定是否需要構(gòu)造事件。

使用moveToThread方式創(chuàng)建線程

moveToThread可以切換指定對(duì)象的所屬線程,該方法不是線程安全的,僅允許在對(duì)象的所在線程將該對(duì)象移動(dòng)到其他線程。也就是說(shuō),將對(duì)象從線程A移動(dòng)到線程B后,可以在線程B里將對(duì)象再移動(dòng)到線程A,但不能在A線程里調(diào)用 moveToThread。

文檔里指明,不允許對(duì)象父子在不同的線程。moveToThread前,不應(yīng)該指定對(duì)象的parent。

QThread * thread=  new QThread();
TestObject * object = new TestObject();
connect(this, SIGNAL(widgetSignal1()), object, SLOT(doTest1()));
object->moveToThread(thread);
thread->start();    //啟動(dòng)線程
emit widgetSignal1();    //觸發(fā)信號(hào)
QTimer::singleShot(1000, this, SIGNAL(widgetSignal1()));
QThread::msleep(10); 
thread->quit();

這段代碼,將TestObject實(shí)例object移動(dòng)到線程,并啟動(dòng)線程,觸發(fā)一次信號(hào),使用QTimer::singleShot延遲1s再次觸發(fā)一次信號(hào)。最后結(jié)束線程事件循環(huán)。測(cè)試結(jié)果顯示,第二次的信號(hào)并沒(méi)有觸發(fā)槽。 因?yàn)槭录h(huán)提前關(guān)閉了。

(休眠10ms是為了避免第一次的信號(hào)觸發(fā)后,線程事件循環(huán)還未開(kāi)始處理就退出了。如果不休眠10ms,多次執(zhí)行這段代碼,第一次信號(hào)還是有概率觸發(fā)槽函數(shù)的,這就是線程。)

如果上面的代碼改成:

QThread * thread=  new QThread();
TestObject * object = new TestObject();
connect(this, SIGNAL(widgetSignal1()), object, SLOT(doTest1()));
object->moveToThread(thread);
thread->start();
QTimer::singleShot(1000, this, SIGNAL(widgetSignal1()));
QTimer::singleShot(2000, thread, SLOT(start()));
thread->quit();

多加一句延遲啟動(dòng)線程,測(cè)試結(jié)果顯示,第二次的信號(hào)觸發(fā)的槽成功執(zhí)行??梢?jiàn)跨線程觸發(fā)信號(hào)會(huì)產(chǎn)生事件并投遞到接收者所在線程隊(duì)列。

在不同的線程中創(chuàng)建對(duì)象

上面所有的測(cè)試代碼都是在主線程創(chuàng)建的對(duì)象,主線程事件循環(huán)一般情況下總是存在的,如果換成 QtConcurrent 或者 std::thread中創(chuàng)建對(duì)象呢?

不用測(cè)試也能推測(cè)出來(lái),如果接收者所在線程不存在事件循環(huán),那么跨線程的觸發(fā)槽不會(huì)觸發(fā),因?yàn)闆](méi)有辦法處理。(但可以在其他線程創(chuàng)建完成后,移動(dòng)到有事件循環(huán)的線程中)。

隊(duì)列阻塞連接

(Qt的信號(hào)槽連接類型還支持隊(duì)列阻塞模式,后面再補(bǔ)充吧)

四、總結(jié)

上面的測(cè)試,也沒(méi)有把所有可能的情況覆蓋。比如再引入QEventLoop可能會(huì)出現(xiàn)什么問(wèn)題。

最后做個(gè)簡(jiǎn)單的總結(jié),Qt的信號(hào)觸發(fā)時(shí),根據(jù)連接類型、接收者所在線程選擇槽的調(diào)用方式。

  • 自動(dòng)連接,信號(hào)觸發(fā)時(shí)線程 = 接收者所在線程,此時(shí)直接調(diào)用
  • 自動(dòng)連接,信號(hào)觸發(fā)時(shí)線程 ≠ 接收者所在線程,產(chǎn)生事件投遞到接收者線程事件循環(huán)
  • 如果是隊(duì)列連接,產(chǎn)生事件投遞到接收者線程事件循環(huán)

也就是,信號(hào)的觸發(fā)不關(guān)心觸發(fā)者所在線程有沒(méi)有事件循環(huán)。只有選擇了隊(duì)列方式,產(chǎn)生了事件,才會(huì)依賴接收者所在的事件循環(huán)處理。因此,信號(hào)總是會(huì)觸發(fā),如果槽沒(méi)有執(zhí)行,也是接收者的問(wèn)題。

五、另外一些問(wèn)題

std::thread和QtConcurrent接口創(chuàng)建的線程差異

一開(kāi)始我以為信號(hào)的觸發(fā)也對(duì)線程有一定的要求,比如必須是QThread。但實(shí)際std::thread內(nèi)也可以觸發(fā)信號(hào)。
在這樣的線程中創(chuàng)建對(duì)象A,并連接其他線程對(duì)象B的信號(hào)到A的槽,QtConcurrent可以在線程生存周期內(nèi),調(diào)用QCoreApplication::processEvents處理對(duì)象B觸發(fā)的信號(hào),而std::thread沒(méi)有這樣的能力??赡躋tConcurrent內(nèi)部是通過(guò)QThread實(shí)現(xiàn)的,std::thread為什么沒(méi)有這樣的能力(畢竟QObject::thread是可以獲取信息的)?

QTimer不能在非QThread線程內(nèi)啟動(dòng),也許也是因?yàn)閮烧叩牟町愐鸬摹?/p>

QTimer::singleShot啟動(dòng)0延時(shí),因?yàn)椴恍枰娴膯?dòng)計(jì)時(shí)器,不依賴線程的隊(duì)列產(chǎn)生超時(shí)事件,又都可以用。

到此這篇關(guān)于淺談Qt信號(hào)槽與事件循環(huán)的關(guān)系的文章就介紹到這了,更多相關(guān)Qt信號(hào)槽與事件循環(huán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解C++編程中的變量相關(guān)知識(shí)

    詳解C++編程中的變量相關(guān)知識(shí)

    這篇文章主要介紹了詳解C++編程中的變量相關(guān)知識(shí),是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-09-09
  • C++實(shí)現(xiàn)二叉樹(shù)非遞歸遍歷算法詳解

    C++實(shí)現(xiàn)二叉樹(shù)非遞歸遍歷算法詳解

    在C++中,二叉樹(shù)非遞歸遍歷是一種常用的算法,可避免遞歸過(guò)程中的系統(tǒng)開(kāi)銷和棧溢出問(wèn)題。非遞歸遍歷算法利用棧數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),可以實(shí)現(xiàn)前序、中序和后序遍歷,是C++程序員必備技能之一
    2023-04-04
  • C++實(shí)現(xiàn)教工考勤信息管理系統(tǒng)

    C++實(shí)現(xiàn)教工考勤信息管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)教工考勤信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • C語(yǔ)言 實(shí)現(xiàn)歸并排序算法

    C語(yǔ)言 實(shí)現(xiàn)歸并排序算法

    這篇文章主要介紹了C語(yǔ)言 實(shí)現(xiàn)歸并排序算法的相關(guān)資料,需要的朋友可以參考下
    2016-11-11
  • Python繪圖之詳解matplotlib

    Python繪圖之詳解matplotlib

    這篇文章主要介紹了Python繪圖之詳解matplotlib,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • C++ STL 四種智能指針的用法詳解

    C++ STL 四種智能指針的用法詳解

    C++ 標(biāo)準(zhǔn)模板庫(kù) STL(Standard Template Library) 一共給我們提供了四種智能指針:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr,今天給大家詳細(xì)介紹這幾種指針的具體用法,需要的朋友參考下吧
    2021-06-06
  • C語(yǔ)言順序表實(shí)現(xiàn)代碼排錯(cuò)

    C語(yǔ)言順序表實(shí)現(xiàn)代碼排錯(cuò)

    這篇文章主要介紹了C語(yǔ)言順序表實(shí)現(xiàn)方法,大家參考使用吧
    2013-12-12
  • C++模板以及實(shí)現(xiàn)vector實(shí)例詳解

    C++模板以及實(shí)現(xiàn)vector實(shí)例詳解

    模板是為了實(shí)現(xiàn)泛型編程,所謂泛型編程,就是指編寫與類型無(wú)關(guān)的代碼,下面這篇文章主要給大家介紹了關(guān)于C++模板以及實(shí)現(xiàn)vector的相關(guān)資料,需要的朋友可以參考下
    2021-11-11
  • C語(yǔ)言編程函數(shù)指針入門精講教程

    C語(yǔ)言編程函數(shù)指針入門精講教程

    大家在C語(yǔ)言的學(xué)習(xí)中一定會(huì)接觸指針這樣一個(gè)東西,而指針也是新手路上一定要消滅的boss,如果以后還要學(xué)習(xí)Java的同學(xué)更是要注重指針的學(xué)習(xí),希望能夠有所幫助
    2021-10-10
  • C++函數(shù)指針與指針函數(shù)有哪些關(guān)系和區(qū)別

    C++函數(shù)指針與指針函數(shù)有哪些關(guān)系和區(qū)別

    函數(shù)指針是一個(gè)指針變量,它可以存儲(chǔ)函數(shù)的地址,然后使用函數(shù)指針,這篇文章主要介紹了C++中函數(shù)指針與指針函數(shù)有哪些關(guān)系和區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值
    2022-08-08

最新評(píng)論

龙泉市| 青田县| 扶风县| 孟津县| 平遥县| 延津县| 远安县| 犍为县| 图木舒克市| 江油市| 同德县| 镇坪县| 云和县| 济阳县| 攀枝花市| 辉南县| 南丰县| 蒙自县| 且末县| 搜索| 盖州市| 东阳市| 三门峡市| 登封市| 崇义县| 中牟县| 高青县| 石柱| 陇南市| 罗江县| 乌兰察布市| 米易县| 虹口区| 且末县| 铜陵市| 宜兰市| 沂水县| 富川| 西平县| 广河县| 开封县|