QTimer와 이벤트 순환 및 다중 스레드

16838 단어 Qt
타이머의 원본 코드 분석startTimer은 타이머의 ID를 되돌려줍니다. 타이머 시간이 되면 QTimerEvent을 받고 허함수 timerEvent을 덮어쓰고 처리합니다. 이 QTimerEvent는 타이머 ID를 포함합니다.
QTimer의 소스 코드를 보면 알 수 있습니다.
QObject::startTimer()
{
        if (Q_UNLIKELY(!d->threadData->eventDispatcher.load())) {
        qWarning("QObject::startTimer: Timers can only be used with threads started with QThread");
        return 0;
    }
    if (Q_UNLIKELY(thread() != QThread::currentThread())) {
        qWarning("QObject::startTimer: Timers cannot be started from another thread");
        return 0;
    }
    //          eventDispatcher      ,killTimer  unregisterTimer
    int timerId = d->threadData->eventDispatcher.load()->registerTimer(interval, timerType, this);
    if (!d->extraData)
        d->extraData = new QObjectPrivate::ExtraData;
    d->extraData->runningTimers.append(timerId);
    return timerId;
}

이벤트 디스패치는 모든 QObject 대상과 관련된 타이머의 목록을 유지하고 registerTimer의 원본 코드를 보십시오:
 void QEventDispatcherWin32::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *object)
{
    if (timerId < 1 || interval < 0 || !object) {
        qWarning("QEventDispatcherWin32::registerTimer: invalid arguments");
        return;
    } else if (object->thread() != thread() || thread() != QThread::currentThread()) {
        //        event dispatcher    
        qWarning("QEventDispatcherWin32::registerTimer: timers cannot be started from another thread");
        return;
    }

    Q_D(QEventDispatcherWin32);
    // exiting ... do  not  register  new timers (QCoreApplication::closingDown()  is  set  too late  to  be  used  here)
    if (d->closingDown)
        return;
        //      ID,  ,  
    WinTimerInfo *t = new WinTimerInfo;
    t->dispatcher = this;
    t->timerId  = timerId;
    t->interval = interval;
    t->timerType = timerType;
    t->obj  = object;
    t->inTimerEvent = false;
    t->fastTimerId = 0;
    if (d->internalHwnd)
        d->registerTimer(t);    //            

    d->timerVec.append(t);                      // store in timer vector
    d->timerDict.insert(t->timerId, t);          // store timers in dict
}

QAbstract Event Dispatcher::registeredTimers는 QObject 대상과 관련된 타이머의 목록을 조회하는 데 사용됩니다.즉 QList list;으로 구성체 TimerInfo는 내연 함수를 제외하고 다음과 같이 구성됩니다.
int timerId;
int interval;
Qt::TimerType   timerType;

    enum   TimerType {      //                 
        PreciseTimer,
        CoarseTimer,
        VeryCoarseTimer
    };

이어서 내부 실현 클래스의 동명 함수를 보면 이번에는 좀 복잡하다.
void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
{
    //    internal window handle used for socketnotifiers/timers/etc
    Q_ASSERT(internalHwnd);
    Q_Q(QEventDispatcherWin32);

    bool ok = false;
    calculateNextTimeout(t, qt_msectime());
    uint   interval = t->interval;
    if (interval == 0u) {
        //       0 timer,         ,    QZeroTimerEvent         
        QCoreApplication::postEvent(q, new QZeroTimerEvent(t->timerId));
        ok = true;
    } else if (interval < 20u || t->timerType == Qt::PreciseTimer) {
        //    20ms     Precise,         (fast timer)
        t->fastTimerId = timeSetEvent(interval, 1, qt_fast_timer_proc, DWORD_PTR(t),
                                    TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS );
        ok = t->fastTimerId;
    }   
    if (!ok) {
        //            ,   (Very)CoarseTimers  ,  SetTimer  
        WM_TIMER           QEvent::Timer   QObject。
        ok = SetTimer(internalHwnd, t->timerId, interval, 0);
    }
    if (!ok)        //       
        qErrnoWarning("QEventDispatcherWin32::registerTimer: Failed to create a timer");
}

코드는 시간 간격에 따라 세 가지 처리 방식을 제시했다.1. 간격이 0이므로 더 이상 설명하지 않습니다.timeSetEvent, 리셋 함수 qt_fast_timer_proc을 받아들입니다. 만기가 되면 리셋되고 독립된 라인에서 호출됩니다.리셋 함수post 메시지를 발송기에 보내면 발송기는 이 메시지를 얻고 QEvent::Timer으로 보내며 원본 코드를 보십시오:
void WINAPI QT_WIN_CALLBACK qt_fast_timer_proc(uint timerId, uint /*reserved*/, DWORD_PTR user, DWORD_PTR /*reserved*/, DWORD_PTR /*reserved*/)
{
    if (!timerId)               // sanity check
        return;
    WinTimerInfo *t = (WinTimerInfo*)user;
    Q_ASSERT(t);
    QCoreApplication::postEvent(t->dispatcher, new QTimerEvent(t->timerId));
}
  • 은 20ms 이상의 간격으로 SetTimer를 사용하여 WM을 전송합니다.TIMER 메시지는 콜백 함수 qt_internal_proc에 전송되며 QEvent::Timer로 QObject에 전송됩니다.콜백 함수의 일부 코드:
  • else if (message == WM_TIMER) {
            Q_ASSERT(d != 0);
            d->sendTimerEvent(wp);
            return 0;
        }

    다음을 찾습니다.
    QEventDispatcherWin32Private::sendTimerEvent
    {
        WinTimerInfo *t = timerDict.value(timerId);
        if (t && !t->inTimerEvent) {
            // send event, but don't allow it to recurse
            t->inTimerEvent = true;
    
            QTimerEvent e(t->timerId);
            //     QTimerEvent  
            QCoreApplication::sendEvent(t->obj, &e);
            . . . . . . 
        }
    }

    이상 발송된 QEvent::Timer사건은 모두 QObject::timerEvent에서 처리되었습니다.timerEvent()에서 timeout() 신호 전송:
    void QTimer::timerEvent(QTimerEvent *e)
    {
        if (e->timerId() == id) {
            if (single)
                stop();
            emit timeout(QPrivateSignal());
        }
    }

    타이머 ID
    타이머 ID는 분배 알고리즘에 따라 얻어진 것으로 틀림없이 유일하다(심지어 크로스 라인에서).QObject가 한 라인에서 다른 라인으로 이동할 때 (moveToThread) 타이머도 함께 이동합니다.이동 타이머는 오래된 라인의 송신기에서 타이머를 간단하게 취소하고 새 라인의 송신기에 등록하는 문제입니다.타이머의 ID가 고유하지 않으면 타이머 ID가 새 스레드에 있는 기존 타이머와 충돌할 수 있습니다.
    moveToThread의 3대 임무 중 세 번째는 현재 라인에 있는 timer 등록을 해제하고 목표 라인에 다시 등록하는 것이다.이유는 제1조: 함수는sendEvent()를 통해 QEvent::ThreadChange 이벤트를 발송하고 QObject::이벤트에서 처리합니다.이벤트 함수의 일부 원본을 보십시오:
        case QEvent::ThreadChange: {
            Q_D(QObject);
            QThreadData *threadData = d->threadData;
            QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.load();
            if (eventDispatcher) {
                QList<QAbstractEventDispatcher::TimerInfo> timers = eventDispatcher->registeredTimers(this);
                if (!timers.isEmpty()) {
                    // do  not  to  release  our  timer  ids  back  to  the pool (since the timer ids are moving to  a  new  thread).
                    //     
                    eventDispatcher->unregisterTimers(this);
                    QMetaObject::invokeMethod(this, "_q_reregisterTimers", Qt::QueuedConnection,
                                              Q_ARG(void*, (new QList<QAbstractEventDispatcher::TimerInfo>(timers))));      }       }
            break;
        }

    또 함수 _q_reregisterTimers을 비동기적으로 호출했는데 그 중에서 eventDispatcher->registerTimer(ti.timerId, ti.interval, ti.timerType, q);은 재등록을 실현했다.
    다중 스레드에서 타이머 사용
    위의 분석을 통해 알 수 있듯이 다중 스레드에서 타이머를 사용할 때 반드시 같은 스레드에서 타이머를 시작하고 정지해야 한다. 즉, 타이머를 만드는 스레드에서만 timeout() 신호를 받아들일 수 있다. 일반적으로 차 스레드의 run 함수이다.그렇지 않으면 startTimer와registerTimer에서 오류가 발생합니다.또 다른 방법은 타이머와 작업 종류를 모두 하위 라인으로 옮기는 것이다.
    지금 이 프로그램을 보면, 스레드에서 타이머를 켜고, 매초에 랜덤수를 만들고, 주 스레드의 텍스트 상자에 랜덤수를 하나씩 추가합니다.
    class MyObj : public QObject
    {
        Q_OBJECT
    public:
        MyObj();
    signals:
        void toLine(QString line);
    private slots:
        void doWork();
        void testTimer();
    private:
        QTimer* timer;
    };
    
    void MyObj::doWork()
    {
        qDebug()<<"timer thread:"<<:currentthread timer="<span" class="hljs-keyword">new QTimer();
        connect(timer,SIGNAL(timeout()),this,SLOT(testTimer()));
        timer->start(2000);
    }
    
    void MyObj::testTimer()
    {
        QString str = QString::number(qrand()%100);
        qDebug()<<"test timer:"<

    다음 라인에서timer를 만들고 열었습니다
    마스터 스레드 섹션의 코드:
    t = new QThread();
    obj = new MyObj();
    obj->moveToThread(t);
    qDebug()<<"main thread:"<<QThread::currentThread();
    connect(t,SIGNAL(started()), obj, SLOT(doWork()), Qt::QueuedConnection);
    connect(obj,SIGNAL(toLine(QString)),this,SLOT(appendText(QString) ),Qt::QueuedConnection );
    connect(t,SIGNAL(finished()), obj, SLOT(deleteLater()) );
    t->start();

    전편의 코드와 거의 같아서 더 이상 설명하지 않는다.
    참고: Timer에 대한 Qt 공식 설명

    좋은 웹페이지 즐겨찾기