QT事件循环

文章目录

    • 主事件循环
    • 事件循环
    • 事件调度器
    • 事件处理
      • 投递事件
      • 发送事件
    • 事件循环的嵌套
    • 线程的事件循环
    • deleteLater与事件循环
    • QEventLoop类
    • QEventLoop应用
      • 等待一段时间
      • 同步操作
      • 模拟模态对话框
    • 参考

本文主要对QT中的事件循环做简单介绍和使用

Qt作为一个跨平台的UI框架,其事件循环实现原理, 就是把不同平台的事件循环进行了封装,并提供统一的抽象接口。

主事件循环

一个最简单的qt程序:

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    
    return app.exec();
}

QCoreApplication app(argc, argv)这一步,主要是创建并初始化了一个QCoreApplication对象。最主要的是设置一些线程相关的数据(QThreadData),比如事件调度器(QAbstractEventDispatcher)等。
app.exec()则是为了启动一个事件循环来分发事件。如果没有事件循环或事件循环没有启动,则对象永远不会接收到事件。
由QCoreApplication启动的事件循环也叫作主事件循环,所有的事件分发、事件处理都从这里开始。

查看exec的代码(qtbase/src/corelib/kernel/qcoreapplication.cpp):

int QCoreApplication::exec()
{
    if (!QCoreApplicationPrivate::checkInstance("exec"))
        return -1;

    QThreadData *threadData = self->d_func()->threadData;
    if (threadData != QThreadData::current()) {
        qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }
    if (!threadData->eventLoops.isEmpty()) {
        qWarning("QCoreApplication::exec: The event loop is already running");
        return -1;
    }

    threadData->quitNow = false;
    QEventLoop eventLoop;
    self->d_func()->in_exec = true;
    self->d_func()->aboutToQuitEmitted = false;
    int returnCode = eventLoop.exec();
    threadData->quitNow = false;

    if (self)
        self->d_func()->execCleanup();

    return returnCode;
}

可以看到,基本就是启动一个QEventLoop。

事件循环

继续查看QEventLoop的代码(qtbase/src/corelib/kernel/qeventloop.cpp):

int QEventLoop::exec(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    auto threadData = d->threadData.loadRelaxed();

    //we need to protect from race condition with QThread::exit
    QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(threadData->thread.loadAcquire()))->mutex);
    if (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);

            auto threadData = d->threadData.loadRelaxed();
            ++threadData->loopLevel;
            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();
            auto threadData = d->threadData.loadRelaxed();
            QEventLoop *eventLoop = threadData->eventLoops.pop();
            Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
            Q_UNUSED(eventLoop); // --release warning
            d->inExec = false;
            --threadData->loopLevel;
        }
    };
    LoopReference ref(d, locker);

    // 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 (threadData->loopLevel > 1)
        emscripten_sleep(1);
#endif

    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);

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

可见QEventLoop::exec()是通过循环不断地调用QEventLoop::processEvents()来分发事件队列中的事件。
而最终完成事件分发的是事件调度器。通过下面的代码我们可以看出,事件调度器存在于各自线程相关数据中。也就是说每个线程都可以有、并且只使用自己专属的事件调度器。

bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    auto threadData = d->threadData.loadRelaxed();
    if (!threadData->hasEventDispatcher())
        return false;
    return threadData->eventDispatcher.loadRelaxed()->processEvents(flags);
}

事件调度器

事件调度器就比较依赖于各个平台的实现了,各平台上的事件调度器实现都不尽相同。Linux下是QEventDispatcherUNIX:

bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    Q_D(QEventDispatcherUNIX);
    d->interrupt.storeRelaxed(0);

    // we are awake, broadcast it
    emit awake();

    auto threadData = d->threadData.loadRelaxed();
    QCoreApplicationPrivate::sendPostedEvents(nullptr, 0, 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 = (threadData->canWaitLocked()
                          && !d->interrupt.loadRelaxed()
                          && wait_for_events);

    if (canWait)
        emit aboutToBlock();

    if (d->interrupt.loadRelaxed())
        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);
}

QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData)会派发当前线程事件队列中所有的事件。

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;

        if (!pe.event)
            continue;
        if ((receiver && receiver != pe.receiver) || (event_type && event_type != pe.event->type())) {
            data->canWait = false;
            continue;
        }

        if (pe.event->type() == QEvent::DeferredDelete) {
            // DeferredDelete events are sent either
            // 1) when the event loop that posted the event has returned; or
            // 2) if explicitly requested (with QEvent::DeferredDelete) for
            //    events posted by the current event loop; or
            // 3) if the event was posted before the outermost event loop.

            int eventLevel = static_cast<QDeferredDeleteEvent *>(pe.event)->loopLevel();
            int loopLevel = data->loopLevel + data->scopeLevel;
            const bool allowDeferredDelete =
                (eventLevel > loopLevel
                 || (!eventLevel && loopLevel > 0)
                 || (event_type == QEvent::DeferredDelete
                     && eventLevel == loopLevel));
            if (!allowDeferredDelete) {
                // cannot send deferred delete
                if (!event_type && !receiver) {
                    // we must copy it first; we want to re-post the event
                    // with the event pointer intact, but we can't delay
                    // nulling the event ptr until after re-posting, as
                    // addEvent may invalidate pe.
                    QPostEvent pe_copy = pe;

                    // null out the event so if sendPostedEvents recurses, it
                    // will ignore this one, as it's been re-posted.
                    const_cast<QPostEvent &>(pe).event = nullptr;

                    // re-post the copied event so it isn't lost
                    data->postEventList.addEvent(pe_copy);
                }
                continue;
            }
        }

        // first, we diddle the event so that we can deliver
        // it, and that no one will try to touch it later.
        pe.event->posted = false;
        QEvent *e = pe.event;
        QObject * r = pe.receiver;

        --r->d_func()->postedEvents;
        Q_ASSERT(r->d_func()->postedEvents >= 0);

        // next, update the data structure so that we're ready
        // for the next event.
        const_cast<QPostEvent &>(pe).event = nullptr;

        locker.unlock();
        const auto relocker = qScopeGuard([&locker] { locker.lock(); });

        QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked)

        // after all that work, it's time to deliver the event.
        QCoreApplication::sendEvent(r, e);

        // careful when adding anything below this point - the
        // sendEvent() call might invalidate any invariants this
        // function depends on.
    }

    cleanup.exceptionCaught = false;
}

事件处理

Application还提供了sendEvent和poseEvent两个函数,分别用来发送事件。sendEvent发出的事件会立即被处理,也就是“同步”执行。postEvent发送的事件会被加入事件队列,在下一轮事件循环时才处理,也就是“异步”执行。还有一个特殊的sendPostedEvents,是将已经加入队列中的准备异步执行的事件立即同步执行。

QCoreApplication::sendPostedEvents()会清空事件队列并立即返回。

void QCoreApplication::sendPostedEvents(QObject *receiver, int event_type)
{
    // ### Qt 6: consider splitting this method into a public and a private
    //           one, so that a user-invoked sendPostedEvents can be detected
    //           and handled properly.
    QThreadData *data = QThreadData::current();

    QCoreApplicationPrivate::sendPostedEvents(receiver, event_type, data);
}

而QCoreApplication::processEvents()相较于QCoreApplication::sendPostedEvents()相当于多了等待事件发生这一步。也就是说它在清空事件队列后不会立即返回。

void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    QThreadData *data = QThreadData::current();
    if (!data->hasEventDispatcher())
        return;
    data->eventDispatcher.loadRelaxed()->processEvents(flags);
}

投递事件

当通过QCoreApplication::postEvent()投递一个事件的时候,会将其加入到事件队列中,并唤醒事件调度器。具体来说,是向eventfd()系统调用获取的文件描述符写入数据,使其状态为数据可读(POLLIN)。
You can manually post events to any object in any thread at any time using the thread-safe function QCoreApplication::postEvent(). The events will automatically be dispatched by the event loop of the thread where the object was created.

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()) {
        // remember the current running eventloop for DeferredDelete
        // events posted in the receiver's thread.

        // Events sent by non-Qt event handlers (such as glib) may not
        // have the scopeLevel set correctly. The scope level makes sure that
        // code like this:
        //     foo->deleteLater();
        //     qApp->processEvents(); // without passing QEvent::DeferredDelete
        // will not cause "foo" to be deleted before returning to the event loop.

        // If the scope level is 0 while loopLevel != 0, we are called from a
        // non-conformant code path, and our best guess is that the scope level
        // should be 1. (Loop level 0 is special: it means that no event loops
        // are running.)
        int loopLevel = data->loopLevel;
        int scopeLevel = data->scopeLevel;
        if (scopeLevel == 0 && loopLevel != 0)
            scopeLevel = 1;
        static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
    }

    // delete the event on exceptions to protect against memory leaks till the event is
    // properly owned in the postEventList
    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();
}

发送事件

postEvent()、sendEvent()是Qt事件循环中两个不同的阶段。如果简化掉一切中间流程,可以认为QCoreApplication::sendEvent()是在直接调用接受者的QObject::event()方法(QGuiApplication及QApplication实现不尽相同)。
Event filters are supported in all threads, with the restriction that the monitoring object must live in the same thread as the monitored object. Similarly, QCoreApplication::sendEvent() (unlike postEvent()) can only be used to dispatch events to objects living in the thread from which the function is called

bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{
    Q_TRACE(QCoreApplication_sendEvent, receiver, event, event->type());

    if (event)
        event->spont = false;
    return notifyInternal2(receiver, event);
}


bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{
    bool selfRequired = QCoreApplicationPrivate::threadRequiresCoreApplication();
    if (!self && selfRequired)
        return false;

    // Make it possible for Qt Script to hook into events even
    // though QApplication is subclassed...
    bool result = false;
    void *cbdata[] = { receiver, event, &result };
    if (QInternal::activateCallbacks(QInternal::EventNotifyCallback, cbdata)) {
        return result;
    }

    // Qt enforces the rule that events can only be sent to objects in
    // the current thread, so receiver->d_func()->threadData is
    // equivalent to QThreadData::current(), just without the function
    // call overhead.
    QObjectPrivate *d = receiver->d_func();
    QThreadData *threadData = d->threadData;
    QScopedScopeLevelCounter scopeLevelCounter(threadData);
    if (!selfRequired)
        return doNotify(receiver, event);
    return self->notify(receiver, event);
}

bool QCoreApplication::notify(QObject *receiver, QEvent *event)
{
    // no events are delivered after ~QCoreApplication() has started
    if (QCoreApplicationPrivate::is_app_closing)
        return true;
    return doNotify(receiver, event);
}

static bool doNotify(QObject *receiver, QEvent *event)
{
    if (receiver == nullptr) {                        // serious error
        qWarning("QCoreApplication::notify: Unexpected null receiver");
        return true;
    }

#ifndef QT_NO_DEBUG
    QCoreApplicationPrivate::checkReceiverThread(receiver);
#endif

    return receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}


bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{
    // Note: when adjusting the tracepoints in here
    // consider adjusting QApplicationPrivate::notify_helper too.
    Q_TRACE(QCoreApplication_notify_entry, receiver, event, event->type());
    bool consumed = false;
    bool filtered = false;
    Q_TRACE_EXIT(QCoreApplication_notify_exit, consumed, filtered);

    // send to all application event filters (only does anything in the main thread)
    if (QCoreApplication::self
            && receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread()
            && QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {
        filtered = true;
        return filtered;
    }
    // send to all receiver event filters
    if (sendThroughObjectEventFilters(receiver, event)) {
        filtered = true;
        return filtered;
    }

    // deliver the event
    consumed = receiver->event(event);
    return consumed;
}

事件循环的嵌套

事件循环是可以嵌套的,当在子事件循环中的时候,父事件循环中的事件实际上处于中断状态,当子循环跳出exec之后才可以执行父循环中的事件。当然,这不代表在执行子循环的时候,类似父循环中的界面响应会被中断,因为往往子循环中也会有父循环的大部分事件,执行QMessageBox::exec(),QEventLoop::exec()的时候,虽然这些exec()打断了main()中的QApplication::exec(),但是由于GUI界面的响应已经被包含到子循环中了,所以GUI界面依然能够得到响应。如果某个子事件循环仍然有效,但其父循环被强制跳出,此时父循环不会立即执行跳出,而是等待子事件循环跳出后,父循环才会跳出。

如果子widget没有accept或ignore该事件,则该事件会被传递给其父窗口。那么:对于一个继承而来的类,只要我们重写实现了其各个事件处理函数,则对应的事件肯定无法传递给其父widget! 哪怕重写的该事件处理函数的函数体为空!如果是标准的控件对象,则其肯定没重写各个事件处理函数。那消息能不能传递到父widget中,则取决于中途有没有使用事件过滤器等将该信号拦截下来了。

案例:
在一个QWidget上建了一个QLabel。而后实现父QWidget的mousePressEvent(), 然后跟一下发现:当我click这个label时:居然能进入到父QWidget的mousePressEvent()中,但是如果把子改成QPushButton则进入不了。
(1)对于QLabel: 如果不重写mouse处理函数,也没有设置事件过滤器等操作的话,则相当于:其对mouse这个事件一直没有进行处理,那没有进行处理的话,相当于上边所说的情况,此时该事件会被传递给其parent。
(2)而对于QPushButton而言:当click它时:其会发射clicked()信号,其实这就相当于它对该事件的一个operator过程。所以:这里它accept该事件并进行了对应处理。从而:无法传递给其父窗口。

线程的事件循环

Threads and QObjects
QThread::exec()将开启一个新的事件循环,每个线程的事件循环独立,并且只为存在于其线程内的QObjects对象传递事件,这些对象分为两类,一类是在其线程内创建的,一类是通过moveToThread()移动到该线程内的。可通过thread()方法查看对象依附的线程(thread affinity)。
对象、线程、事件循环的关系如下图:
在这里插入图片描述

deleteLater与事件循环

deleteLater()将发送一个QEvent::DeferredDelete事件到事件循环中。
deleteLater()真正删除对象:

  • 当控制返回事件循环时,该对象将被删除
  • 如果调用deleteLater()时事件循环未运行,一旦事件循环开始,该对象将被删除
  • 如果在循环停止后调用deleteLater(),该对象将在线程完成时被销毁(Qt 4.8以后)

注意,进入和离开新的事件循环(例如QDialog::exec())将不会执行延迟删除,控制权必须返回到调用deleteLater()时所在的事件循环时,对象才会被删除。

QEventLoop类

QEventLoop即Qt中的事件循环类,主要接口如下:

// 进入事件循环直到exit被调用
int exec(QEventLoop::ProcessEventsFlags flags = AllEvents)
// 通知事件循环退出
void exit(int returnCode = 0)
// 判断事件循环是否运行
bool isRunning() const
// 处理符合标志的待处理事件,直到没有更多事件需要处理为止。
bool processEvents(QEventLoop::ProcessEventsFlags flags = AllEvents)
// 处理与标志匹配且最多处理 maxTime 毫秒的待处理事件,或者直到没有更多事件可处理为止,以较短者为准。如果有一个长时间运行的操作,并且希望在不允许用户输入的情况下显示其进度(即通过使用 ExcludeUserInputEvents 标志),则此函数特别有用。
// 注意事项:
// 此函数不会持续处理事件;它会在所有可用事件处理完毕后返回。
// 指定 WaitForMoreEvents 标志没有意义,将被忽略。
void processEvents(QEventLoop::ProcessEventsFlags flags, int maxTime)
// 唤醒事件循环
void wakeUp()

QEventLoop应用

等待一段时间

    QEventLoop loop;
    QTimer::singleShot(1000, &loop, SLOT(quit()));
    qInfo() << QTime::currentTime();
    loop.exec();
    qInfo() << QTime::currentTime();

同步操作

经常会有这种场景: “触发 ”了某项操作,必须等该操作完成后才能进行“ 下一步 ”,但在等待该操作完成期间不能阻塞主线程。比如:数据获取,向服务器发起登录请求后,必须等收到服务器返回的数据,才决定下一步如何执行。这种场景,如果设计成异步调用,直接用Qt的信号/槽即可,如果要设计成同步调用,就可以使用本地QEventLoop。

void A::onFinish()
{
 //槽中退出事件循环 
  loop.quit(); 
}void A::work()
 { 
    // 1.声明本地EventLoop 
    QEventLoop loop;
   // 2. 先连接好信号 
   connect(pwaitPeer, &waitPeer::finished,this,&A::onFinish);
 	// 3. 发起操作
  	...
// 4. 启动事件循环。阻塞当前函数调用,但是事件循环还能运行。这里不会再往下运行,直到前面的槽中,调用loop.quit之后,才会继续往下走
 loop.exec();
}

模拟模态对话框

QDialog dlg;
dlg.show();
QEventLoop loop;
connect(&dlg, SIGNAL(finished(int)), &loop, SLOT(quit()));
loop.exec(QEventLoop::ExcludeUserInputEvents);

参考

QEventLoop
Threads and QObjects

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/972997.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3-知识图谱-知识图谱的存储与查询

基于关系型数据库的知识图谱存储 基于原生图的知识图谱存储 关系型数据库的局限性 因为关系数据库&#xff0c;不善于处理“关系” 图数据库&#xff1a; Relations Are First-class citizens 在关系数据库中&#xff0c;关系是隐藏表达的。通过外键关联实体&#xff0c;表达…

【HarmonyOS Next】鸿蒙监听手机按键

【HarmonyOS Next】鸿蒙监听手机按键 一、前言 应用开发中我们会遇到监听用户实体按键&#xff0c;或者扩展按键的需求。亦或者是在某些场景下&#xff0c;禁止用户按下某些按键的业务需求。 这两种需求&#xff0c;鸿蒙都提供了对应的监听事件进行处理。 onKeyEvent 默认的…

SpringCloud-Eureka初步使用

什么是REST是一组用于规范资源在网络中转移的表现形式软件架构设计风格.简单来说就是客户端和服务器之间的一种交互形式 什么是RESTful,满足了REST风格的接口或者程序,RESTful API是其中的接口,spring中提供了RestTemplate这个类,他强制执行了REST的规范,包括使用HTTP协议的状…

SpringBoot+uniApp日历备忘录小程序系统 附带详细运行指导视频

文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码1.日历渲染代码&#xff1a;2.保存备忘录代码&#xff1a;3.删除备忘录代码&#xff1a; 一、项目演示 项目演示地址&#xff1a; 视频地址 二、项目介绍 项目描述&#xff1a;这是一个基于SpringBootuniApp框架开…

推荐给 Easysearch 新用户的几个 Elasticsearch 可视化工具

Easysearch 作为国产化的 Elasticsearch&#xff08;ES&#xff09;替代方案&#xff0c;兼容 Elasticsearch 生态系统中的多种工具。本文将介绍几款适合 Easysearch 用户的可视化工具&#xff0c;帮助您更高效地管理和查询数据。 1. Elasticsearch Head 插件 在ES培训经常提…

PHP+Apache+MySQL安装(Windows)

一、安装教程 参考链接1 参考链接2 二、问题描述 PHP安装目录下找不到php8apache2_4.dll PHP安装包下载错误 Apache Service Monitor: request operation has failed! 定位问题&#xff1a; 查看【事件查看器】 解决问题 安装或更新与PHP版本相对应的Visual C Redistribu…

捷米特 JM - RTU - TCP 网关应用 F - net 协议转 Modbus TCP 实现电脑控制流量计

一、项目背景 在某工业生产园区的供水系统中&#xff0c;为了精确监测和控制各个生产环节的用水流量&#xff0c;需要对分布在不同区域的多个流量计进行集中管理。这些流量计原本采用 F - net 协议进行数据传输&#xff0c;但园区的监控系统基于 Modbus TCP 协议进行数据交互&…

【Mysql】我在广州学Mysql 系列—— 有关日志管理的示例

ℹ️大家好&#xff0c;我是练小杰&#xff0c;今天星期四了&#xff0c;明天周五&#xff0c;美好的周末又要到了&#xff01;&#xff01;&#x1f606; 本文是对MySQL日志管理内容进行练习&#xff0c;后续将添加更多相关知识噢&#xff0c;谢谢各位的支持&#x1f64f; 复习…

JUC并发—8.并发安全集合二

大纲 1.JDK 1.7的HashMap的死循环与数据丢失 2.ConcurrentHashMap的并发安全 3.ConcurrentHashMap的设计介绍 4.ConcurrentHashMap的put操作流程 5.ConcurrentHashMap的Node数组初始化 6.ConcurrentHashMap对Hash冲突的处理 7.ConcurrentHashMap的并发扩容机制 8.Concu…

金融时间序列【量化理论】

业界常用的技术分析指标都与价格本身有关&#xff0c;而时间序列分析由于对数据平稳性的要求常常是基于收益率这样更加偏稳定的数据&#xff08;收益率由于会涨停和跌停每天最多10%&#xff09; 平稳性&#xff1a; 强平稳性&#xff1a;随时间变化&#xff0c;各个统计特征都…

nvm安装、管理node多版本以及配置环境变量【保姆级教程】

引言 不同的项目运行时可能需要不同的node版本才可以运行&#xff0c;由于来回进行卸载不同版本的node比较麻烦&#xff1b;所以需要使用node工程多版本管理。 本人在配置时&#xff0c;通过网络搜索教程&#xff0c;由于文章时间过老&#xff0c;或者文章的互相拷贝导致配置时…

8 SpringBootWeb案例(上): 查询【分页功能(分页插件)】、删除、新增、修改

文章目录 前言:SpringBootWeb案例1. 准备工作1.1 需求&环境搭建1.1.1 需求说明1.1.2 环境搭建1.2 开发规范1.2.1 开发规范-REST(不强求非要这种风格,传统风格有时候更方便)1.2.2 开发规范-统一响应结果和异常处理1.2.3 开发流程2. 部门管理2.1 查询部门2.1.1 原型和需求…

新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)

视频教程和更多福利在我主页简介或专栏里 &#xff08;不懂都可以来问我 专栏找我哦&#xff09; 如果对你有帮助你可以来专栏找我&#xff0c;我可以无偿分享给你对你更有帮助的一些经验和资料哦 目录&#xff1a; 一、XSS的三种类型&#xff1a; 二、XSS攻击的危害&#x…

用《软件方法》引导AI全流程高效开发

和“敏捷”的拍脑袋“试错”不同&#xff0c;《软件方法》一直强调严谨地思考、推导和建模。 如何尽量借助现有AI的力量&#xff0c;降低建模人员A→B→C→D的推导工作量&#xff0c;是一个非常有价值的课题。我们将用一个实例来分享和展示《软件方法》作者潘加宇的建议实践。…

全面收集中间件Exporter适配:从Redis到ActiveMQ,掌握监控数据采集的最佳实践

#作者&#xff1a;任少近 文章目录 说明&#xff1a;一 Redis的适配exporter版1.1 Redis的exporter源码版本1.2 Redis的exporter的releases版1.3 Redis_exporter版本选择理由1.4 Redis_exporter docer镜像 二 Zookeeper的适配exporter版2.1 Zookeeper的exporter源码版本2.2 Zo…

npm在install时提示要安装python问题处理

使用npm\yarn\pnpm下载以来的时候&#xff0c;一直提示python异常&#xff0c;有的项目安装了python之后&#xff0c;下载依赖还是异常 而且旧版本项目使用python2,新的使用Python3…很烦 解决方案1&#xff1a;cnpm 使用cnpm 安装教程&#xff1a; npm安装cnpm&#xff0c;解…

浅谈网络 | 容器网络之Cilium

目录 Cilium介绍Cilium是什么Cilium 主要功能特性为什么用Cilium&#xff1f; 功能概述组件概况BPF 与 XDPeBPF (Extended Berkeley Packet Filter)XDP (eXpress Data Path) Cilium介绍 Cilium是什么 Cilium 是一个开源网络和安全项目&#xff0c;专为 Kubernetes、Docker 和…

前端知识点---vue的声明周期(vue)

文章目录 创建挂载更新销毁 vue的生命周期有四个阶段: 创建 挂载, 更新和销毁 创建 是vue组件从创建到准备渲染的过程 dom还没挂载到页面中 进行了初始化工作: 初始化数据(data,props) . 设置计算属性computed 初始化方法 methods 绑定事件watch 创建阶段的钩子函数beforeCrea…

MySQL如何解决幻读?

目录 一、什么是幻读&#xff1f; 1.1 幻读的定义 1.2 幻读的示例 1.3 幻读产生的原因&#xff1f; 1.4?读已提交&#xff08;Read Committed&#xff09; 1.4.1 确定事务等级 1.4.2 非锁定读取 准备 示例 结论 1.4.3 锁定读取 准备 示例 分析 结论 1.5?可重…

Day15-后端Web实战-登录认证——会话技术JWT令牌过滤器拦截器

目录 登录认证1. 登录功能1.1 需求1.2 接口文档1.3 思路分析1.4 功能开发1.5 测试 2. 登录校验2.1 问题分析2.2 会话技术2.2.1 会话技术介绍2.2.2 会话跟踪方案2.2.2.1 方案一 - Cookie2.2.2.2 方案二 - Session2.2.2.3 方案三 - 令牌技术 2.3 JWT令牌2.3.1 介绍2.3.2 生成和校…