【问题标题】:Is the timer being started from another thread?计时器是从另一个线程启动的吗?
【发布时间】:2020-04-24 23:47:48
【问题描述】:

QThread 文档提出了两种让代码在单独线程中运行的方法。如果我继承 QThread 并重新实现 run(),那么我会得到 ​​p>

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();
};        

【问题讨论】:

  • 你构造了没有父级的计时器,所以当worker移动到它的线程时它不会被移动。
  • 你是说 QBasicTimer 计时器?请问我应该如何构建它?
  • @TobySpeight 我不这么认为,计时器是在run() 函数的开头创建并在结尾销毁的。所以当moveToThread() 被调用时它甚至不存在。我认为问题在于,不可能从另一个线程捕获定时器的超时,而不是从它启动的线程(停止它也是一样)。
  • @KcFnMi 在您的 moveToThread 示例中,run() 函数创建了一个计时器,启动它并在之后立即退出(顺便说一句,这将破坏计时器)。
  • 此外,要在另一个线程中启动计时器,您还需要在相应线程上执行事件循环,否则您将永远不会收到计时器事件。

标签: c++ multithreading qt qthread qtimer


【解决方案1】:

关于第一个(非moveToThread)示例...

快速查看QBasicTimer::start 的 Qt 源代码显示以下内容...

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 指的是由MainWidget 在主GUI 线程上创建的QWorker 实例。因此发出警告。

编辑 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 的线程亲和性:它必须是当前正在运行的线程。

【讨论】:

  • 我已经在QObject 上下文中(我的意思是已经在做#include &lt;QObject&gt;)所以我应该使用QTimer 而不是QBasicTimer。我应该这样想吗?
  • @G.M:您能否详细说明问题 (3):为什么它与 moveToThread() 一起使用?
  • @tunglt 更新了答案。
  • @KcFnMi 如果你想要一个可以用来在不同线程之间发出超时通知的计时器,那么,是的,我会使用QTimer
  • 我担心定时器线程(不是线程之间)的超时通知。即使在这样的情况下,我是否应该期望使用 QBasicTimer 获得更好的性能?
猜你喜欢
  • 1970-01-01
  • 2013-08-09
  • 2017-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-22
  • 1970-01-01
  • 2012-01-22
相关资源
最近更新 更多