QEventLoop

事件系统

事件系统是QT一个很重要的机制,事件循环则由QEventLoop实现。
此处拿QMenu举例。

QT源代码基于5.12分析

QMenu menu;
QAction *clear = menu.addAction(“test”);
menu.exec(QCursor::pos());
QAction *QMenu::exec(const QPoint &p, QAction *action)
{
    Q_D(QMenu); // 预处理后 QMenuPrivate * const d = d_func()
    // QMenu的头文件使用宏 “Q_DECLARE_PRIVATE(QMenu)" 声明了d_func
    ensurePolished();
    createWinId();
    QEventLoop eventLoop;
    d->eventLoop = &eventLoop;
    popup(p, action);

    QPointer<QObject> guard = this;
    (void) eventLoop.exec(); // 此处执行后会阻塞当前线程,进入事件循环
    if (guard.isNull())
        return 0;

    action = d->syncAction;
    d->syncAction = 0;
    d->eventLoop = 0;
    return action;
}

下面步入正题,QEventLoop的设计思想

class Q_CORE_EXPORT QEventLoop : public QObject
{
    Q_OBJECT
    Q_DECLARE_PRIVATE(QEventLoop)

public:
    explicit QEventLoop(QObject *parent = nullptr);
    ~QEventLoop();

    enum ProcessEventsFlag {
        AllEvents = 0x00,
        ExcludeUserInputEvents = 0x01,
        ExcludeSocketNotifiers = 0x02,
        WaitForMoreEvents = 0x04,
        X11ExcludeTimers = 0x08,
        EventLoopExec = 0x20,
        DialogExec = 0x40
    };
    Q_DECLARE_FLAGS(ProcessEventsFlags, ProcessEventsFlag)

    bool processEvents(ProcessEventsFlags flags = AllEvents);
    void processEvents(ProcessEventsFlags flags, int maximumTime);

    int exec(ProcessEventsFlags flags = AllEvents);
    void exit(int returnCode = 0);
    bool isRunning() const;

    void wakeUp();

    bool event(QEvent *event) override;

public Q_SLOTS:
    void quit();
};
QEventLoop::QEventLoop(QObject *parent)
    : QObject(*new QEventLoopPrivate, parent)
{
    Q_D(QEventLoop);
    if (!QCoreApplication::instance() && QCoreApplicationPrivate::threadRequiresCoreApplication()) {
        qWarning("QEventLoop: Cannot be used without QApplication");
    } else {
        d->threadData->ensureEventDispatcher();
    }
}

QEventLoop不会创建线程,但是其执行会借用调用线程来完成事件循环,QEventLoop的构造会保证其有一个事件分发器,对于不同平台其事件分发器调用的底层API也不相同,事件分发器包括QEventDispatcherUNIX,QEventDispatcherGlib,QEventDispatcherWin32,QEventDispatcherWinRT。在此以QEventDispatcherUNIX为例进行分析

int QEventLoop::exec(ProcessEventsFlags flags)
{
    QEventLoopPrivate * const d = d_func();
    //we need to protect from race condition with QThread::exit
    QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread))->mutex);
    if (d->threadData->quitNow)
        return -1;

    if (d->inExec) {
        qWarning("QEventLoop::exec: instance %p has already called exec()", this);
        return -1;
    }

    struct LoopReference {
        QEventLoopPrivate *d;
        QMutexLocker &locker;

        bool exceptionCaught;
        LoopReference(QEventLoopPrivate *d, QMutexLocker &locker) : d(d), locker(locker), exceptionCaught(true)
        {
            d->inExec = true;
            d->exit.storeRelease(false);
            ++d->threadData->loopLevel;
            d->threadData->eventLoops.push(d->q_func());
            locker.unlock();
        }

        ~LoopReference()
        {
            if (exceptionCaught) {
                qWarning("Qt has caught an exception thrown from an event handler. Throwing\n"
                         "exceptions from an event handler is not supported in Qt.\n"
                         "You must not let any exception whatsoever propagate through Qt code.\n"
                         "If that is not possible, in Qt 5 you must at least reimplement\n"
                         "QCoreApplication::notify() and catch all exceptions there.\n");
            }
            locker.relock();
            QEventLoop *eventLoop = d->threadData->eventLoops.pop();
            Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
            Q_UNUSED(eventLoop); // --release warning
            d->inExec = false;
            --d->threadData->loopLevel;
        }
    };
    LoopReference ref(d, locker);	// C++ RAII机制,用来退出后的清理操作

    // remove posted quit events when entering a new event loop
    QCoreApplication *app = QCoreApplication::instance();
    if (app && app->thread() == thread())
        QCoreApplication::removePostedEvents(app, QEvent::Quit);

#ifdef Q_OS_WASM
    // Partial support for nested event loops: Make the runtime throw a JavaSrcript
    // exception, which returns control to the browser while preserving the C++ stack.
    // Event processing then continues as normal. The sleep call below never returns.
    // QTBUG-70185
    if (d->threadData->loopLevel > 1)
        emscripten_sleep(1);
#endif

	// 事件循环
    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);

    ref.exceptionCaught = false;
    return d->returnCode.load();
}

processEvents会调用分发器的processEvents

bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop); // 预处理后 QEventLoopPrivate * const d = d_func()
    if (!d->threadData->hasEventDispatcher())
        return false;
    return d->threadData->eventDispatcher.load()->processEvents(flags);
}
bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    Q_D(QEventDispatcherUNIX);
    d->interrupt.store(0);

    // we are awake, broadcast it
    emit awake();
    QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData);

    const bool include_timers = (flags & QEventLoop::X11ExcludeTimers) == 0;
    const bool include_notifiers = (flags & QEventLoop::ExcludeSocketNotifiers) == 0;
    const bool wait_for_events = flags & QEventLoop::WaitForMoreEvents;

    const bool canWait = (d->threadData->canWaitLocked()
                          && !d->interrupt.load()
                          && wait_for_events);

    if (canWait)
        emit aboutToBlock();

    if (d->interrupt.load())
        return false;

    timespec *tm = nullptr;
    timespec wait_tm = { 0, 0 };

    if (!canWait || (include_timers && d->timerList.timerWait(wait_tm)))
        tm = &wait_tm;

    d->pollfds.clear();
    d->pollfds.reserve(1 + (include_notifiers ? d->socketNotifiers.size() : 0));

    if (include_notifiers)
        for (auto it = d->socketNotifiers.cbegin(); it != d->socketNotifiers.cend(); ++it)
            d->pollfds.append(qt_make_pollfd(it.key(), it.value().events()));

    // This must be last, as it's popped off the end below
    d->pollfds.append(d->threadPipe.prepare());

    int nevents = 0;

    switch (qt_safe_poll(d->pollfds.data(), d->pollfds.size(), tm)) {
    case -1:
        perror("qt_safe_poll");
        break;
    case 0:
        break;
    default:
        nevents += d->threadPipe.check(d->pollfds.takeLast());
        if (include_notifiers)
            nevents += d->activateSocketNotifiers();
        break;
    }

    if (include_timers)
        nevents += d->activateTimers();

    // return true if we handled events, false otherwise
    return (nevents > 0);
}

qt_safe_poll会调用系统底层API,对于UNIX,其调用的是select函数,调用路径
qt_safe_poll->qt_ppoll->qt_poll->select
QT需要通过定义宏#define QT_FEATURE_poll_poll 1配置poll才能使用poll

处理哪些事件

QEventLoop::processEvents会处理套接字及QEventLoop创建的事件描述符以及定时事件,自建的事件描述符一般是通过eventfd创建,用来唤醒阻塞在select的线程

问题遗留

暂时未找到调用QEventLoop::exit的位置,从程序现象来看,当鼠标右击,在其他位置在次右击时会先打断之前的菜单,在创建一个新的。找到这块代码分析,则才是一个完整事件系统

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值