QT核心-事件循环其他用途

先总结一下前面几章:

  • 生存线程就是QObject的event及其衍生方法的执行线程。
  • 事件循环内部为一个死循环,它会托管当前线程并将当前线程中的事件分发给相应的对象,且对象的事件执行函数(event)所在的执行线程为当前线程。
  • 当开启一个新事件循环时,会阻塞当前流程,类似于while(true)的效果。但与之不同的是while(true)在没有退出循环前当前线程会一直阻塞,什么都得不到执行,但事件循环仅仅只是阻塞当前流程,如果当前线程还有其他事件,这些事件仍然能够得到执行。
  • 同一个线程中同一时间只会有一个事件循环生效,当一个事件循环生效,并在事件执行函数(event)中再次开启一个新的事件循环时会阻塞老事件循环,新的事件循环会代替老事件循环分发剩余事件。
线程同步

多线程是编程中永远绕不开的事,那么线程同步同样也是编程中永远绕不开的问题。需要线程同步的情况有很多种,有如下一种情况:

  1. 存在一个业务处理线程,专门处理长耗时任务。
  2. 主线程有两个长耗时任务,且这两个任务有先后顺序。

那么最常规的编程方案可以是: 业务线程中存在一个任务队列,run函数中不断加锁、从任务队列提取任务执行,并不断循环。外部调用某个函数添加任务,该函数加锁、向任务队列追加并退出函数。
其实锁和队列QT已经自带了,我们完全可以只使用QT提供的接口就能完成这个功能。

  • 创建一个继承至QObject的类ObjectTest,并重写QObject的虚函数customEvent,这个函数会处理QEvent::Type的值大于等于QEvent::User的事件
// .h
#pragma once

#include <QObject>

class ObjectTest : public QObject
{
    Q_OBJECT
public:
    explicit ObjectTest(QObject *parent = nullptr);

    void customEvent(QEvent *event) override;
};
//.cpp
#include "ObjectTest.h"

#include <QDebug>
#include <QEvent>
#include <QThread>

ObjectTest::ObjectTest(QObject *parent) : QObject(parent)
{
}

void ObjectTest::customEvent(QEvent *event)
{
    qDebug() << event->type() << QThread::currentThread();
}
  • 构造一个对象和一个新的线程,并使用moveToThread将对象的生存线程移动至新的线程中,并向对象中抛事件
ObjectTest o;   // 构造一个对象o
QThread t;      // 构造一个新的线程
t.start();      // 启动该线程
o.moveToThread(&t); // 将对象o的生存线程从主线程移动至新的线程中
qDebug() << "main thread:" << QThread::currentThread();
qApp->postEvent(&o, new QEvent(static_cast<QEvent::Type>(QEvent::User)));   // 向对象o中抛一个事件
qApp->postEvent(&o, new QEvent(static_cast<QEvent::Type>(QEvent::User + 1)));   // 向对象o中再抛一个新的事件

运行结果

main thread: QThread(0x1f9463ae1e0)
QEvent::User QThread(0x3c039ff6b8)
QEvent::Type(1001) QThread(0x3c039ff6b8)

根据以上结果可以看到,事件的运行线程和主线程不同,达到了多线程的效果,抛出的两个事件完全按照抛出时的循序先后执行。
多线程就不多说了,就是本文章第一句话。先后循序能得以保证是因为postEvent所抛出的事件会追加到事件队列中,而队列是一种先进先出的数据结构。这里没有体现线程安全,因为这里的两个事件都是在主线程中抛出的,按照我的习惯直接看postEvent的源码去证明其线程安全性(QT助手也有明确说明: Note: This function is thread-safe.),毕竟要做测试的话要花很大的功夫。
postEvent的源码压缩一下并去掉注释如下:

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) {
        delete event;
        return;
    }
    QThreadData *data = locker.threadData;
    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<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
    }
    QScopedPointer<QEvent> 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();
    QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
    if (dispatcher)
        dispatcher->wakeUp();
}

加锁和解锁的两行代码为auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);locker.unlock();看到它加个锁都要单独写个函数出来我也就放心了,总比自己调QMutex::lock函数好,你可以信任QT。

上面的方案可以让某一个线程安全的按照循序去执行任务,但是外面没法同步等到任务结束。要么通过信号槽、要么使用回调函数异步的去通知外面的线程。其实有一种办法可以实现同步效果的,并且也是QT提供的接口。使用QMetaObject::invokeMethod函数,并将第三个参数置为Qt::BlockingQueuedConnection即可,但是这种办法是依赖了元对象的,并且还有一些注意事项,感兴趣可以查一查,这里不多说,后面有机会说到元对象的时候再介绍这个函数吧。

事件循环应该还有其他用途,短时间内记不太清,后面想到再慢慢补吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值