QEventLoop概念原理及应用

分享一篇技术文章,从概念原理及应用分析QEventLoop问题,其他qt工程师遇到此类问题可以快速解决、提高软件质量。

概念

事件循环本质上就是一个无限循环,不停地去获取下一个事件,然后做出处理;直到 quit 事件发生,循环结束。QEventLoop事件循环,调用exec();会被阻塞。直到while循环退出。

QEventLoop用不好会出现代码异常、程序卡死、崩溃的现象。qt封装了很多,不知道原理就去用?出现问题碰运气解决或者不解决?

原理

猜想,qt多线程的信号槽原理是多线程的生产者消费者模型。QEventLoop::exec();事件循环处理本线程所有事件。

一个错误的例子,本例中Slot1();槽函数代码“loop.exec(QEventLoop::ExcludeUserInputEvents);”后面的流程没有处理到,出现Slot1();在exec();中调用其他槽函数(这里例子指Slot1();递归)的现象。

 引用:QT信号槽机制分析_同一个槽函数避免覆盖的问题-CSDN博客

(7).活用慎用processEvents,线程(包括主线程和其它线程)执行很繁重的计算任务中,为防止消息事件一直无机会处理,则在函数中手动调用processEvents,让消息事件有机会更新。界面不会假死。另一方面,槽函数中不可调用processEvents,因为这会导致“中断”当前槽函数,进而去执行消息队列中后续的槽函数,可能会引发同一槽函数重入问题,将编程问题复杂化。
————————————————
版权声明:本文为CSDN博主「抬头仰望的天空」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lo_ve18/article/details/115693667

我的例子与分析: 

#pragma once

#include 
#include "ui_QtWidgetsApplication1.h"

class QtWidgetsApplication1 : public QWidget
{
    Q_OBJECT

public:
    QtWidgetsApplication1(QWidget *parent = nullptr);
    ~QtWidgetsApplication1();
signals:
    void Sig1();
private slots:
    void Slot1();
    void on_pushButton_clicked();
private:
    Ui::QtWidgetsApplication1Class ui;
};
#include "QtWidgetsApplication1.h"
#include 
#include 
#include 
#include 
#include 
#include 
QtWidgetsApplication1::QtWidgetsApplication1(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
	connect(this, SIGNAL(Sig1()), this, SLOT(Slot1()), Qt::QueuedConnection);
	//模拟工作者线程通知有需要获取网络数据的情况,时间频率不定
    std::thread([this]() {
		while (1)
		{
			emit Sig1();
			std::this_thread::sleep_for(std::chrono::milliseconds(100));
		}
		}).detach();
}

QtWidgetsApplication1::~QtWidgetsApplication1()
{}

void QtWidgetsApplication1::Slot1()
{
    //本义以阻塞模式获取网络数据,超时值5s,允许ui卡顿
	QEventLoop loop;
	//其他网络请求代码与loop以信号槽的形式绑定,代码省略
	QTimer::singleShot(5000, &loop, SLOT(quit()));//网络请求超时实现
	loop.exec(QEventLoop::ExcludeUserInputEvents);//排除用户鼠标键盘事件
	//其他业务流程代码...
	static int i = 0;
	qDebug() << __FUNCTION__ << "," << i++;
}

void QtWidgetsApplication1::on_pushButton_clicked()
{
}

发现其他槽函数调用递归于loop.exec(QEventLoop::ExcludeUserInputEvents);,调用堆栈:

 	QtWidgetsApplication1.exe!QtWidgetsApplication1::Slot1() 行 27	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) 行 84	C++
 	Qt5Cored.dll!QMetaCallEvent::placeMetaCall(QObject * object) 行 618	C++
 	Qt5Cored.dll!QObject::event(QEvent * e) 行 1314	C++
 	Qt5Widgetsd.dll!QWidget::event(QEvent * event) 行 9080	C++
 	Qt5Widgetsd.dll!QApplicationPrivate::notify_helper(QObject * receiver, QEvent * e) 行 3632	C++
 	Qt5Widgetsd.dll!QApplication::notify(QObject * receiver, QEvent * e) 行 3582	C++
 	Qt5Cored.dll!QCoreApplication::notifyInternal2(QObject * receiver, QEvent * event) 行 1063	C++
 	Qt5Cored.dll!QCoreApplication::sendEvent(QObject * receiver, QEvent * event) 行 1459	C++
 	Qt5Cored.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver, int event_type, QThreadData * data) 行 1817	C++
 	Qt5Cored.dll!QEventDispatcherWin32::sendPostedEvents() 行 1082	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::sendPostedEvents() 行 81	C++
 	Qt5Cored.dll!QEventDispatcherWin32::processEvents(QFlags flags) 行 530	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::processEvents(QFlags flags) 行 73	C++
 	Qt5Cored.dll!QEventLoop::processEvents(QFlags flags) 行 140	C++
 	Qt5Cored.dll!QEventLoop::exec(QFlags flags) 行 232	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::Slot1() 行 33	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) 行 84	C++
 	Qt5Cored.dll!QMetaCallEvent::placeMetaCall(QObject * object) 行 618	C++
 	Qt5Cored.dll!QObject::event(QEvent * e) 行 1314	C++
 	Qt5Widgetsd.dll!QWidget::event(QEvent * event) 行 9080	C++
 	Qt5Widgetsd.dll!QApplicationPrivate::notify_helper(QObject * receiver, QEvent * e) 行 3632	C++
 	Qt5Widgetsd.dll!QApplication::notify(QObject * receiver, QEvent * e) 行 3582	C++
 	Qt5Cored.dll!QCoreApplication::notifyInternal2(QObject * receiver, QEvent * event) 行 1063	C++
 	Qt5Cored.dll!QCoreApplication::sendEvent(QObject * receiver, QEvent * event) 行 1459	C++
 	Qt5Cored.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver, int event_type, QThreadData * data) 行 1817	C++
 	Qt5Cored.dll!QEventDispatcherWin32::sendPostedEvents() 行 1082	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::sendPostedEvents() 行 81	C++
 	Qt5Cored.dll!QEventDispatcherWin32::processEvents(QFlags flags) 行 530	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::processEvents(QFlags flags) 行 73	C++
 	Qt5Cored.dll!QEventLoop::processEvents(QFlags flags) 行 140	C++
 	Qt5Cored.dll!QEventLoop::exec(QFlags flags) 行 232	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::Slot1() 行 33	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) 行 84	C++
 	Qt5Cored.dll!QMetaCallEvent::placeMetaCall(QObject * object) 行 618	C++
 	Qt5Cored.dll!QObject::event(QEvent * e) 行 1314	C++
 	Qt5Widgetsd.dll!QWidget::event(QEvent * event) 行 9080	C++
 	Qt5Widgetsd.dll!QApplicationPrivate::notify_helper(QObject * receiver, QEvent * e) 行 3632	C++
 	Qt5Widgetsd.dll!QApplication::notify(QObject * receiver, QEvent * e) 行 3582	C++
 	Qt5Cored.dll!QCoreApplication::notifyInternal2(QObject * receiver, QEvent * event) 行 1063	C++
 	Qt5Cored.dll!QCoreApplication::sendEvent(QObject * receiver, QEvent * event) 行 1459	C++
 	Qt5Cored.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver, int event_type, QThreadData * data) 行 1817	C++
 	Qt5Cored.dll!QEventDispatcherWin32::sendPostedEvents() 行 1082	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::sendPostedEvents() 行 81	C++
 	Qt5Cored.dll!QEventDispatcherWin32::processEvents(QFlags flags) 行 530	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::processEvents(QFlags flags) 行 73	C++
 	Qt5Cored.dll!QEventLoop::processEvents(QFlags flags) 行 140	C++
 	Qt5Cored.dll!QEventLoop::exec(QFlags flags) 行 232	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::Slot1() 行 33	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) 行 84	C++
 	Qt5Cored.dll!QMetaCallEvent::placeMetaCall(QObject * object) 行 618	C++
 	Qt5Cored.dll!QObject::event(QEvent * e) 行 1314	C++
 	Qt5Widgetsd.dll!QWidget::event(QEvent * event) 行 9080	C++
 	Qt5Widgetsd.dll!QApplicationPrivate::notify_helper(QObject * receiver, QEvent * e) 行 3632	C++
 	Qt5Widgetsd.dll!QApplication::notify(QObject * receiver, QEvent * e) 行 3582	C++
 	Qt5Cored.dll!QCoreApplication::notifyInternal2(QObject * receiver, QEvent * event) 行 1063	C++
 	Qt5Cored.dll!QCoreApplication::sendEvent(QObject * receiver, QEvent * event) 行 1459	C++
 	Qt5Cored.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver, int event_type, QThreadData * data) 行 1817	C++
 	Qt5Cored.dll!QEventDispatcherWin32::sendPostedEvents() 行 1082	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::sendPostedEvents() 行 81	C++
 	Qt5Cored.dll!QEventDispatcherWin32::processEvents(QFlags flags) 行 530	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::processEvents(QFlags flags) 行 73	C++
 	Qt5Cored.dll!QEventLoop::processEvents(QFlags flags) 行 140	C++
 	Qt5Cored.dll!QEventLoop::exec(QFlags flags) 行 232	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::Slot1() 行 33	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) 行 84	C++
 	Qt5Cored.dll!QMetaCallEvent::placeMetaCall(QObject * object) 行 618	C++
 	Qt5Cored.dll!QObject::event(QEvent * e) 行 1314	C++
 	Qt5Widgetsd.dll!QWidget::event(QEvent * event) 行 9080	C++
 	Qt5Widgetsd.dll!QApplicationPrivate::notify_helper(QObject * receiver, QEvent * e) 行 3632	C++
 	Qt5Widgetsd.dll!QApplication::notify(QObject * receiver, QEvent * e) 行 3582	C++
 	Qt5Cored.dll!QCoreApplication::notifyInternal2(QObject * receiver, QEvent * event) 行 1063	C++
 	Qt5Cored.dll!QCoreApplication::sendEvent(QObject * receiver, QEvent * event) 行 1459	C++
 	Qt5Cored.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver, int event_type, QThreadData * data) 行 1817	C++
>	Qt5Cored.dll!QEventDispatcherWin32::sendPostedEvents() 行 1082	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::sendPostedEvents() 行 81	C++
 	Qt5Cored.dll!QEventDispatcherWin32::processEvents(QFlags flags) 行 530	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::processEvents(QFlags flags) 行 73	C++
 	Qt5Cored.dll!QEventLoop::processEvents(QFlags flags) 行 140	C++
 	Qt5Cored.dll!QEventLoop::exec(QFlags flags) 行 232	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::Slot1() 行 33	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) 行 84	C++
 	Qt5Cored.dll!QMetaCallEvent::placeMetaCall(QObject * object) 行 618	C++
 	Qt5Cored.dll!QObject::event(QEvent * e) 行 1314	C++
 	Qt5Widgetsd.dll!QWidget::event(QEvent * event) 行 9080	C++
 	Qt5Widgetsd.dll!QApplicationPrivate::notify_helper(QObject * receiver, QEvent * e) 行 3632	C++
 	Qt5Widgetsd.dll!QApplication::notify(QObject * receiver, QEvent * e) 行 3582	C++
 	Qt5Cored.dll!QCoreApplication::notifyInternal2(QObject * receiver, QEvent * event) 行 1063	C++
 	Qt5Cored.dll!QCoreApplication::sendEvent(QObject * receiver, QEvent * event) 行 1459	C++
 	Qt5Cored.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver, int event_type, QThreadData * data) 行 1817	C++
 	Qt5Cored.dll!QEventDispatcherWin32::sendPostedEvents() 行 1082	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::sendPostedEvents() 行 81	C++
 	Qt5Cored.dll!QEventDispatcherWin32::processEvents(QFlags flags) 行 530	C++
 	qwindowsd.dll!QWindowsGuiEventDispatcher::processEvents(QFlags flags) 行 73	C++
 	Qt5Cored.dll!QEventLoop::processEvents(QFlags flags) 行 140	C++
 	Qt5Cored.dll!QEventLoop::exec(QFlags flags) 行 232	C++
 	Qt5Cored.dll!QCoreApplication::exec() 行 1371	C++
 	Qt5Guid.dll!QGuiApplication::exec() 行 1868	C++
 	Qt5Widgetsd.dll!QApplication::exec() 行 2825	C++
 	QtWidgetsApplication1.exe!main(int argc, char * * argv) 行 9	C++
 	QtWidgetsApplication1.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) 行 97	C++

生产者线程投递产品:

 	Qt5Cored.dll!QCoreApplication::postEvent(QObject * receiver, QEvent * event, int priority) 行 1587	C++
 	Qt5Cored.dll!queued_activate(QObject * sender, int signal, QObjectPrivate::Connection * c, void * * argv) 行 3762	C++
 	Qt5Cored.dll!doActivate<0>(QObject * sender, int signal_index, void * * argv) 行 3847	C++
 	Qt5Cored.dll!QMetaObject::activate(QObject * sender, const QMetaObject * m, int local_signal_index, void * * argv) 行 3947	C++
 	QtWidgetsApplication1.exe!QtWidgetsApplication1::Sig1() 行 145	C++
>	QtWidgetsApplication1.exe!`QtWidgetsApplication1::QtWidgetsApplication1'::`2'::::operator()() 行 17	C++

选择投递接收者线程,本线程:

QCoreApplicationPrivate::QPostEventListLocker QCoreApplicationPrivate::lockThreadPostEventList(QObject *object)
{
    QPostEventListLocker locker;

    if (!object) {
        locker.threadData = QThreadData::current();
        locker.locker = qt_unique_lock(locker.threadData->postEventList.mutex);
        return locker;
    }

 投递:

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
    Q_TRACE_SCOPE(QCoreApplication_postEvent, receiver, event, event->type());

    if (receiver == nullptr) {
        qWarning("QCoreApplication::postEvent: Unexpected null receiver");
        delete event;
        return;
    }

    auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
    if (!locker.threadData) {
        // posting during destruction? just delete the event to prevent a leak
        delete event;
        return;
    }

    QThreadData *data = locker.threadData;

    // if this is one of the compressible events, do compression
    if (receiver->d_func()->postedEvents
        && self && self->compressEvent(event, receiver, &data->postEventList)) {
        Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event);
        return;
    }

    if (event->type() == QEvent::DeferredDelete)
        receiver->d_ptr->deleteLaterCalled = true;

    if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) {
      
        int loopLevel = data->loopLevel;
        int scopeLevel = data->scopeLevel;
        if (scopeLevel == 0 && loopLevel != 0)
            scopeLevel = 1;
        static_cast(event)->level = loopLevel + scopeLevel;
    }

    // delete the event on exceptions to protect against memory leaks till the event is
    // properly owned in the postEventList
    QScopedPointer eventDeleter(event);
    Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());
    data->postEventList.addEvent(QPostEvent(receiver, event, priority));
    eventDeleter.take();
    event->posted = true;
    ++receiver->d_func()->postedEvents;
    data->canWait = false;
    locker.unlock();

消费者线程(ui线程)取产品:

D:\Qt\5.15.2\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp

void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type,
                                               QThreadData *data)
{
    if (event_type == -1) {
        // we were called by an obsolete event dispatcher.
        event_type = 0;
    }

    if (receiver && receiver->d_func()->threadData != data) {
        qWarning("QCoreApplication::sendPostedEvents: Cannot send "
                 "posted events for objects in another thread");
        return;
    }

    ++data->postEventList.recursion;

    auto locker = qt_unique_lock(data->postEventList.mutex);

    // by default, we assume that the event dispatcher can go to sleep after
    // processing all events. if any new events are posted while we send
    // events, canWait will be set to false.
    data->canWait = (data->postEventList.size() == 0);

    if (data->postEventList.size() == 0 || (receiver && !receiver->d_func()->postedEvents)) {
        --data->postEventList.recursion;
        return;
    }

    data->canWait = true;

    // okay. here is the tricky loop. be careful about optimizing
    // this, it looks the way it does for good reasons.
    int startOffset = data->postEventList.startOffset;
    int &i = (!event_type && !receiver) ? data->postEventList.startOffset : startOffset;
    data->postEventList.insertionOffset = data->postEventList.size();

    // Exception-safe cleaning up without the need for a try/catch block
    struct CleanUp {
        QObject *receiver;
        int event_type;
        QThreadData *data;
        bool exceptionCaught;

        inline CleanUp(QObject *receiver, int event_type, QThreadData *data) :
            receiver(receiver), event_type(event_type), data(data), exceptionCaught(true)
        {}
        inline ~CleanUp()
        {
            if (exceptionCaught) {
                // since we were interrupted, we need another pass to make sure we clean everything up
                data->canWait = false;
            }

            --data->postEventList.recursion;
            if (!data->postEventList.recursion && !data->canWait && data->hasEventDispatcher())
                data->eventDispatcher.loadRelaxed()->wakeUp();

            // clear the global list, i.e. remove everything that was
            // delivered.
            if (!event_type && !receiver && data->postEventList.startOffset >= 0) {
                const QPostEventList::iterator it = data->postEventList.begin();
                data->postEventList.erase(it, it + data->postEventList.startOffset);
                data->postEventList.insertionOffset -= data->postEventList.startOffset;
                Q_ASSERT(data->postEventList.insertionOffset >= 0);
                data->postEventList.startOffset = 0;
            }
        }
    };
    CleanUp cleanup(receiver, event_type, data);

    while (i < data->postEventList.size()) {
        // avoid live-lock
        if (i >= data->postEventList.insertionOffset)
            break;

        const QPostEvent &pe = data->postEventList.at(i);
        ++i;

应用

QEventLoop接管了执行代码所在线程的event事件循环,所以不应调用在槽函数中,若必须在槽函数中调用结合上面的原理,可以这样实现,接修改上面的例子:

void QtWidgetsApplication1::Slot1()
{
	//本义以阻塞模式获取网络数据,超时值5s,允许ui卡顿
	std::thread([]() {
		QEventLoop loop;
		//其他网络请求代码与loop以信号槽的形式绑定,代码省略
		QTimer::singleShot(5000, &loop, SLOT(quit()));//网络请求超时实现
		loop.exec(QEventLoop::ExcludeUserInputEvents);//排除用户鼠标键盘事件
	}).join();
	//其他业务流程代码...
	static int i = 0;
	qDebug() << __FUNCTION__ << "," << i++;
}

参考

qt工作、学习中需要源码调试,qt5.15.2+vs2019源码调试开发环境搭建-CSDN博客

你可能感兴趣的:(qt,c++,qt,QEventLoop)