QStateMachine과 QEventLoop 이벤트 순환의 관계와 차이를 깊이 이해하다

18904 단어 event
최근 계속 되풀이이벤트 사이클하고 있는 물건들, Qt 원본이 얼마나 많은지 보면서 느낀 점을 여기에 기록해 여러분과 공유할 수 있습니다.한 마디로 하면QStateMachine 상태기 자체에 있어QEventLoop::exec()의 드라이브가 있어야만 지원할 수 있다. 즉, Qt 프로그램이 열릴 때 마지막 한마디.
QCoreApplication::exec()

이미 내부에서 상태 순환에 들어갔다
int QCoreApplication::exec()

{

...

    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;

    }



    QEventLoop eventLoop;

    self->d_func()->in_exec = true;

    self->d_func()->aboutToQuitEmitted = false;

    int returnCode = eventLoop.exec();

...

}

위에서 우리는 다음과 같은 몇 가지 결론을 얻을 수 있다.
  • 자연스레 우리는 이벤트 대기열이 하나의 라인, 즉 같은 라인만 관련되어 있음을 알 수 있다. 예를 들어 당신이 어떻게 바꾸느냐에 따라 최종적으로 당신의 이벤트 순환과 이벤트 대기열 자체는 모두 이 라인에 속한다.
  • QApplication::exec () 이런 것들은 QEventLoop::exec () 를 최종적으로 호출하여 이벤트 순환을 형성합니다.

  • 사실 QApplication뿐만 아니라 QDialog에 유사한 exec() 함수가 있다는 것을 우리는 알고 있다. 사실 내부도 부분적인 이벤트 순환에 들어간다.
    int QDialog::exec()
    
    {
    
    ...
    
        QEventLoop eventLoop;
    
        d->eventLoop = &eventLoop;
    
        QPointer<QDialog> guard = this;
    
        (void) eventLoop.exec(QEventLoop::DialogExec);
    
        if (guard.isNull())
    
            return QDialog::Rejected;
    
        d->eventLoop = 0;
    
    ...
    
    }

    이를 통해 알 수 있듯이 QDialog의 이런 exec()는 내부도 결국 하나의 창고에 있는 QEventLoop을 만들어 이벤트 순환을 진행한다.이때 틀림없이 어떤 학우들이 다음과 같은 의문을 가지고 있을 것이다.
  • 만약에 내가 QApplication::exec()에서 QDialog의 exec()를 호출했다면 QEventLoop은 어떻게 비난을 분배합니까?

  • 사실 답은 위에 있다. 한 라인에 대해 말하자면 그가 가진 이벤트 대기열은 유일하지만 그가 가진 이벤트 순환은 여러 개가 될 수 있지만 절대로 끼워 넣는 관계이며 현재 QEventLoop만 활성화된다.QEventLoop의 exec () 내부에서 무엇을 하는지 볼 수 있습니다.
    int QEventLoop::exec(ProcessEventsFlags flags)
    
    {
    
        Q_D(QEventLoop);
    
    ...#if defined(QT_NO_EXCEPTIONS)
    
        while (!d->exit)
    
            processEvents(flags | WaitForMoreEvents | EventLoopExec);
    
    #else
    
        try {
    
            while (!d->exit)
    
                processEvents(flags | WaitForMoreEvents | EventLoopExec);
    
        } catch (...) {
    ...
    }


    그 내부가 바로while 순환을 통해 끊임없는 프로세스 이벤트()를 보이고 프로세스 이벤트()를 살펴보자.
    bool QEventLoop::processEvents(ProcessEventsFlags flags)
    
    {
    
        Q_D(QEventLoop);
    
        if (!d->threadData->eventDispatcher)
    
            return false;
    
        if (flags & DeferredDeletion)
    
            QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
    
        return d->threadData->eventDispatcher->processEvents(flags);
    
    }

    한 라인에 대해 말하자면, 그 사건의 순환이 내부 층에 끼워 넣든 외부 층에 끼워 넣든, 최종적으로 호출될 것이다
    d->threadData->eventDispatcher

    이것은 라인의 유일한 것이기 때문에 우리 위의 결론을 증명했다. 사건 대기열은 라인에 대해 일대일이다.그러면 어떻게 우리의 또 다른 관점을 검증할 수 있습니까? 즉, 같은 라인에서 이벤트 순환은 여러 개가 될 수 있고 끼워 넣는 관계입니다. 현재 하나만 활성화됩니까?작은 데모를 써서 검증해 봅시다.
    MainWindow::MainWindow(QWidget *parent) :
    
        QMainWindow(parent),
    
        ui(new Ui::MainWindow)
    
    {
    
        ui->setupUi(this);
    
    }
    
    
    
    MainWindow::~MainWindow()
    
    {
    
        delete ui;
    
    }
    
    
    
    void MainWindow::on_pushButton_clicked()
    
    {
    
        QDialog dialog;
    
        dialog.exec();
    
    }

    간단합니다. Main Window에 button을 넣으면 그의 클릭 함수에 다이얼로그가 나타나고 국부 이벤트 순환에 들어갑니다. 그 다음에 QEvent Loop::exec () 아래에서 다이얼로그를 열기 전과 열기 후 호출 창고의 차이를 확인합니다.
    0    QEventLoop::processEvents    qeventloop.cpp    144    0xb717dfc3    
    
    1    QEventLoop::exec    qeventloop.cpp    204    0xb717e1cf    
    
    2    QCoreApplication::exec    qcoreapplication.cpp    1225    0xb7181098    
    
    3    QApplication::exec    qapplication.cpp    3823    0xb74c7eaa    
    
    4    main    main.cpp    10    0x804a4ce    

    Dialog를 열기 전에 이벤트 순환이 QCoreApplication 내부에서 제공하는 QEventLoop임을 알 수 있습니다.Dialog를 켠 다음
    0    QEventLoop::processEvents    qeventloop.cpp    144    0xb717dfc3    
    
    1    QEventLoop::exec    qeventloop.cpp    204    0xb717e1cf    
    
    2    QDialog::exec    qdialog.cpp    562    0xb7a949c4    
    
    ...
    
    27    QEventDispatcherGlib::processEvents    qeventdispatcher_glib.cpp    425    0xb71b7cc6    
    
    28    QGuiEventDispatcherGlib::processEvents    qguieventdispatcher_glib.cpp    204    0xb7595140    
    
    29    QEventLoop::processEvents    qeventloop.cpp    149    0xb717e061    
    
    30    QEventLoop::exec    qeventloop.cpp    204    0xb717e1cf    
    
    31    QCoreApplication::exec    qcoreapplication.cpp    1225    0xb7181098    
    
    32    QApplication::exec    qapplication.cpp    3823    0xb74c7eaa    
    
    33    main    main.cpp    10    0x804a4ce    

    이때의 이벤트 순환이 바로 QDialog의 exec()라는 것을 알 수 있다. 사실 내부의 exec()가 퇴장하지 않으면 외부의 exec()를 실행할 수 없다. 그러나 절대로 이때 이벤트가 막힌다고 생각하지 마라. 많은 사람들이 나와 마찬가지로 처음에 QDialog: exec()가 이벤트를 막는다고 생각했는데 사실 이벤트 순환은 계속 처리되고 있다. 유일한 차이점은 바로 이때의 이벤트 순환이 QDialog에 있다는 것이다.
    기본적인 이벤트 순환과 이벤트 대기열을 이해한 후 QStateMachine과 이벤트 순환의 관련을 살펴보겠습니다.
    먼저 QStateMachine의 Post Event() 를 살펴보겠습니다.
    void QStateMachine::postEvent(QEvent *event, EventPriority priority)
    
    {
    
    ...
    
        switch (priority) {
    
        case NormalPriority:
    
            d->postExternalEvent(event);
    
            break;
    
        case HighPriority:
    
            d->postInternalEvent(event);
    
            break;
    
        }
    
        d->processEvents(QStateMachinePrivate::QueuedProcessing);
    
    }

    이를 통해 알 수 있듯이 그는 내부에서 두 개의 대기열을 유지했다. 하나는 보통 우선순위의 external Event Queue이고, 하나는 높은 우선순위의 인터넷 Event Queue이다.이로써 Qt 공식 문서에서 말한 상태기의 이벤트 순환과 대기열은 위에서 언급한 이벤트 대기열과 이벤트 순환은 근본적으로 별개의 일이니 절대 헷갈리지 마세요.프로세싱 이벤트 () 가 진행 중인 것을 확인할 수 있습니다.
    void QStateMachinePrivate::processEvents(EventProcessingMode processingMode)
    
    {
    
        Q_Q(QStateMachine);
    
        if ((state != Running) || processing || processingScheduled)
    
            return;
    
        switch (processingMode) {
    
        case DirectProcessing:
    
            if (QThread::currentThread() == q->thread()) {
    
                _q_process();
    
                break;
    
            } // fallthrough -- processing must be done in the machine thread
    
        case QueuedProcessing:
    
            processingScheduled = true;
    
            QMetaObject::invokeMethod(q, "_q_process", Qt::QueuedConnection);
    
            break;
    
        }
    
    }

    분명히 상태기의 실현 논리는q_프로세스 () 이 비동기 호출을 이벤트 대기열에 넣었습니다. 이것은 공식 문서에서 말한
     Note that this means that it executes asynchronously, and that it will not progress without a running event loop.
    이 말, 즉 상태기의 운행은 현재 라인의 이벤트 대기열에 하나를 잃어버리는 것이다q_프로세스 (), 그리고 이벤트 순환이 그에게 호출되기를 기다리기 때문에 다음 문제의 관건은qt_process()
    void QStateMachinePrivate::_q_process()
    
    {
    ...
     Q_Q(QStateMachine); Q_ASSERT(state
    == Running); Q_ASSERT(!processing); processing = true; processingScheduled = false; while (processing) { if (stop) { processing = false; break; } QSet<QAbstractTransition*> enabledTransitions; QEvent *e = new QEvent(QEvent::None); enabledTransitions = selectTransitions(e); if (enabledTransitions.isEmpty()) { delete e; e = 0; } ... enabledTransitions = selectTransitions(e); if (enabledTransitions.isEmpty()) { delete e; e = 0; } } if (!enabledTransitions.isEmpty()) { q->beginMicrostep(e); microstep(e, enabledTransitions.toList()); q->endMicrostep(e); }#endif if (stop) { stop = false; stopProcessingReason = Stopped;
    ... }

    이를 통해 알 수 있듯이 상태기의 프로세스 자체는 하나의 큰 순환이다. flag은 프로세스ing(이것도 여러 번 배달 q process()의 표기 위치를 피하는 것)이고 이 함수에 들어간 후 상태 이동표에 따라 상응하는 함수를 호출할 수 있다.이 안에는 사실 확장할 수 있는 부분도 있다. 바로 나의 상태기 자체가 호출한 함수가 되돌아오지 않는다는 것이다. 즉,QDialog::exec(), 이벤트 순환에 들어가면 나의 현재 상태 기회는
    microstep(e, enabledTransitions.toList());

    이 함수에서 exec() 함수는 우리가 정상적으로 이벤트 발송을 할 수 있다는 것을 알고 있기 때문에 이벤트 대기열이 상태기 이벤트를 호출할 때 위의 프로세스링이라는 flag의 존재로 인해 우리는
    void QStateMachinePrivate::processEvents(EventProcessingMode processingMode)
    
    {
    
        Q_Q(QStateMachine);
    
        if ((state != Running) || processing || processingScheduled)
    
            return;
    
    ...
    
    }

    즉시 되돌아오기 때문에 상태기의 막힘과 효율 문제를 걱정할 필요가 없습니다. 왜냐하면 이 때 그는 대기열의post 유지보수만 하지만 프로세스 이벤트 ()는 실행할 수 없기 때문입니다.
    이 문제는 또 하나 재미있는 점이 있다. 우리가 이전에 사용했던 언어 환경을 가지고 상태기 자체가 호출한 함수는 QDialog:::exec()를 호출한다. 그러면 다이아로그를 만든 후에 나의 이벤트 순환은 이 다이아로그의 QEvent Loop에서 시작되기 때문에 주의해야 할 점은 바로 나의q_process()
    void QStateMachinePrivate::_q_process()
    
    {
    
        Q_Q(QStateMachine);
    
        Q_ASSERT(state == Running);
    
        Q_ASSERT(!processing);
    
        processing = true;
    
        processingScheduled = false;
    
    #ifdef QSTATEMACHINE_DEBUG
    
        qDebug() << q << ": starting the event processing loop";
    
    #endif
    
        while (processing) {
    
            if (stop) {
    
                processing = false;
    
                break;
    
            }
    
    ...
    
    }

    while 순환이 존재하기 때문에 제 대기열에는 3개의 이벤트가 있을 수 있습니다. A, B, C. 그 중에서 제가 A를 실행할 때 저는 Dialog를 만들었습니다. 이때 제 모든 이벤트 순환은 새로 만든 dialog의 내부에 있는 QEvent Loop에 세워져 있습니다. 그러면 이Dialog를 닫을 때 while는 계속 실행합니다. 그러나 이때 제가 있는 이벤트 순환은 QCore Application의 exec 내부에 있는 QEvent Loop입니다.이 점은 각별한 주의가 필요하다.
    또 하나 주의해야 할 것은 만약에 컨디셔너가 시간 소모 함수를 실행할 때 즉시 되돌아오거나 윗글처럼 Dialog가 나타나게 하려면 컨디셔너가 계속 순환할 수 없지만, 컨디셔너가 다른 이벤트를 정상적으로 처리할 수 있도록 하려면 컨디셔너가 이벤트를 처리하는 내부에서 호출해야 한다.
    bool QMetaObject::invokeMethod();

    이 함수는 세 번째 인자를 통해 Qt::QueuedConnection을 선택하면 이 다이얼로그를 QEvent Loop의 이벤트 대기열에 쉽게 배달하고 현재 상태기를 정상적으로 되돌려줍니다. 그리고 QEvent Loop의 프로세스 Events () 는 이 다이얼로그를 처리하고, 생성된 후에 exec () 를 호출하여 국부 이벤트 순환을 형성합니다.
    전체적으로 다음과 같은 사항에 유의해야 합니다.
  • 이벤트 대기열은 라인에 대해 일대일이고 이벤트 순환은 라인에 대해 다대일이지만 그들은 끼워 넣는 관계이며 현재 QEventLoop만 활성화됩니다.
  • 상태기의 구동은 기존의 이벤트 순환을 통해 추진해야 하며 내부에 유지보수된 이벤트 대기열과QEventLoop의 이벤트 대기열은 별개이다.
  • 상태기의q_process()가 되돌아오지 않을 때 Qt는 더 이상 발송하지 않습니다q_프로세스 이벤트.그리고 언젠가는q_프로세스 순환에서 현재의 모든 상태기 이벤트를 단계적으로 처리합니다.

  • 좋은 웹페이지 즐겨찾기