QT在子线程中正确使用定时器QTimer,及IO类的对象跨线程调用问题

需求:在子线程中使用QTimer定时器定时执行某项任务

正确用法:主要原理,QT对象的线程依附性 “thread affinity

使用 moveToThread 的方式创建子线程,见下文:

头文件:Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QThread>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
    QThread m_thread;
};
#endif // WIDGET_H

实现文件:Widget.cpp

#include "Widget.h"
#include "ui_Widget.h"
#include "TimerThread.h"
#include <QDebug>

Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    qDebug() << "UI thread id:" << QThread::currentThreadId();

    TimerThread* timerInSubThread = new TimerThread();
    timerInSubThread->moveToThread(&m_thread); //使用moveToThread创建子线程的
    connect(&m_thread, SIGNAL(started()), timerInSubThread, SLOT(onCreateTimer())); //在线程启动的时候创建定时器
    connect(&m_thread, &QThread::finished, timerInSubThread, &QObject::deleteLater);
    m_thread.start();
}

Widget::~Widget()
{
    m_thread.quit();
    m_thread.wait();
    delete ui;
}

子线程的类:

头文件:TimerThread.h

#ifndef TIMERTHREAD_H
#define TIMERTHREAD_H

#include <QObject>
#include <QTimer>

class TimerThread : public QObject
{
    Q_OBJECT
public:
    explicit TimerThread(QObject *parent = nullptr);
    ~TimerThread();

public slots:
    void onCreateTimer();
    void onTimeout();

private:
    QTimer* timer = nullptr;

};

#endif // TIMERTHREAD_H

实现文件:TimerThread.cpp

#include "TimerThread.h"
#include <QDebug>
#include <QThread>

TimerThread::TimerThread(QObject *parent) : QObject(parent)
{
}

TimerThread::~TimerThread()
{
    timer->stop();
    timer->deleteLater();
}

void TimerThread::onCreateTimer()
{
    //关键点:在子线程中创建QTimer的对象
    timer = new QTimer();
    timer->setInterval(1000);
    connect(timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
    timer->start();
}

void TimerThread::onTimeout()
{
    qDebug() << " work thread id:" << QThread::currentThreadId(); //打印出线程ID,看看是否UI线程的ID不同
}

运行结果:

 如图,可看出UI线程的ID和定时器所在的线程ID不同,达到预期。

错误用法:

若是将定时器对象的创建放在子线程 TimerThread 类的构造函数,结果会是怎样呢?

见下图:

由输出可以看出,定时器不起效果,提示

“QObject::startTimer: Timers cannot be started from another thread ”

 ——定时器不能在别的线程启动。

我们来分析下:

刚开始只有一个主线程,TimerThread 的实例是在主线程中创建的

TimerThread* timerInSubThread = new TimerThread();

因为定时器是在 TimerThread 的构造函数中

TimerThread::TimerThread(QObject *parent) : QObject(parent)
{
    timer = new QTimer();
}

所以定时器对象也是在主线程中创建的。然后创建子线程,并在子线程中start()定时器,导致对象的创建和start不在同一个线程中,故报错。

具体的原理可参考官方文档——QT对象线程依附性

每个QObject实例都有一个叫做“线程关系”(thread affinity)的属性,或者说,它处于某个线程中。

默认情况下,QObject处于创建它的线程中。

当QObject接收队列信号(queued signal)或者传来的事件(posted event),槽函数或事件处理器将在对象所处的线程中执行。

延伸: 

Qt的所有IO类和QTimer的对象,都不推荐或者不可以跨线程操作,比如说QTcpSocket、QFile、
QUdpSocket等IO类。可以用信号和槽封装一下,或者使用bool QMetaObject::invokeMethod(...)调用

  • 22
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值