Таймер запускается из другого потока? - PullRequest
1 голос
/ 07 января 2020
Документация

QThread предлагает два способа заставить код выполняться в отдельном потоке. Если я подкласс QThread и переопределить run (), то я получу

QBasicTimer::start: Timers cannot be started from another thread  

-

#include <QWidget>
#include <QThread>
#include <QBasicTimer>
#include <QDebug>
#include <QEvent>
#include <QCoreApplication>

class Worker : public QThread
{
    Q_OBJECT
    int id;
    bool m_abort = false;
    bool compute = false;
public:
    Worker() {}

protected:
    void timerEvent(QTimerEvent *event) override {
        if (event->timerId() == id) {
            compute = true;
        } else {
            QObject::timerEvent(event);
        }
    }
public slots:
    void abort() {m_abort = true;}
    void run() {
        qDebug() << QThread::currentThreadId();
        QBasicTimer timer;
        id = timer.timerId();
        timer.start(1000, this);
        forever {
            if (m_abort) break;
            QCoreApplication::processEvents();
            if (compute)
                qDebug() << "computed";
            compute = false;
        }
    }
};

class MainWidget : public QWidget
{
    Q_OBJECT
    QThread thread;
    Worker* worker;
public:
    MainWidget()
    {
        qDebug() << QThread::currentThreadId();
        worker = new Worker;
        worker->start();
    }
    ~MainWidget(){worker->abort();}
};

1) Таймер запускается из другого потока?
2) Почему я не получить это предупреждение при замене QBasicTimer на QTimer?
3) Почему я не получаю это предупреждение при использовании moveToThread?

#include <QWidget>
#include <QThread>
#include <QBasicTimer>
#include <QDebug>
#include <QEvent>
#include <QCoreApplication>

class Worker : public QObject
{
    Q_OBJECT
    QBasicTimer* timer;
    bool m_abort = false;
    bool compute = false;
public:
    Worker() {}

protected:
    void timerEvent(QTimerEvent *event) override {
        if (event->timerId() == timer->timerId()) {
            compute = true;
        } else {
            QObject::timerEvent(event);
        }
    }
public slots:
    void abort() {m_abort = true;}
    void run() {
        timer = new QBasicTimer;
        timer->start(1000, this);
        forever {
            if (m_abort) break;
            QCoreApplication::processEvents();
            if (compute)
                qDebug() << "computed";
            compute = false;
        }
    }
};

class MainWidget : public QWidget
{
    Q_OBJECT
    QThread thread;
    Worker* worker;
public:
    MainWidget()
    {
        worker = new Worker;
        worker->moveToThread(&thread);
        connect(this, &MainWidget::start, worker, &Worker::run);
        thread.start();
        emit start();
    }
    ~MainWidget(){worker->abort(); thread.quit(); thread.wait();}
signals:
    void start();
};        

1 Ответ

3 голосов
/ 07 января 2020

Относительно первого (не moveToThread) примера ...

Беглый взгляд на источник Qt для QBasicTimer::start показывает следующее ...

void QBasicTimer::start(int msec, QObject *obj)
{
    QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance();

    // ...

    if (Q_UNLIKELY(obj && obj->thread() != eventDispatcher->thread())) {
        qWarning("QBasicTimer::start: Timers cannot be started from another thread");
        return;
    }

Таким образом, ожидается, что его второй аргумент obj будет иметь сродство к потоку, равное текущему потоку.

В вашей реализации Worker::run, однако, у вас есть ...

timer.start(1000, this);

В этом контексте текущий поток - это новый поток, созданный экземпляром QThread, но this ссылается на экземпляр QWorker, созданный MainWidget в главном потоке GUI. Отсюда и предупреждение.

Редактировать 1:

На вопрос ...

, почему он работает с moveToThread ()?

Рассмотрим реализацию MainWidget ctor ...

MainWidget()
{
    worker = new Worker;
    worker->moveToThread(&thread);
    connect(this, &MainWidget::start, worker, &Worker::run);
    thread.start();
    emit start();
}

К моменту вызова Worker::run экземпляр Worker был перемещен в новый поток. Таким образом, когда строка ...

timer.start(1000, this);

выполняется, this (что относится к экземпляру Worker) находится в текущем потоке, и тест сходства потока в QBasicTimer::start проходит без предупреждения.

Извините, если вышесказанное немного запутано, но важно учитывать близость потока второго аргумента к QBasicTimer::start: он должен быть текущим запущенным потоком.

...