由此可知,QTimer是不可以跨线程使用的,所以将程序修改成如下,将QTimer的实例创建放到线程里面创建:
/*********在InheritQObject类中添加Init的槽函数,将需要初始化创建的资源放到此处********/ public slots: void Init(){ m_timer = new QTimer(); } /********在MainWindow类中添加InitSiganl()信号,并绑定信号槽***********/ //添加信号 signals: void InitSiganl(); //在MainWindow构造函数添加以下代码 connect(this, &MainWindow::InitSiganl, m_obj, &InheritQObject::Init); //连接信号槽 emit InitSiganl(); //发出信号,启动线程初始化QTimer资源这样QTimer定时器就属于新线程,并且可以正常使用啦。网络套接字QUdpSocket、QTcpSocket等资源同理处理就可以了。
四、如何正确退出线程并释放资源(1)如何正确退出线程?
正确退出线程的方式,其实和往期《子类化QThread实现多线程》中《如何正确退出线程并释放资源》小节所讲到的差不多,就是要使用 quit 和 exit 来退出线程,避免使用 terminate 来强制结束线程,有时候会出现异常的情况。例如以上的实例,启动之后,直接点击 【terminate】按钮,界面就会出现卡死的现象。
(2)如何正确释放线程资源?
在往期《子类化QThread实现多线程》中《如何正确退出线程并释放资源》小节也有讲到,千万别手动delete QThread类或者派生类的线程指针,手动delete会发生不可预料的意外。理论上所有QObject都不应该手动delete,如果没有多线程,手动delete可能不会发生问题,但是多线程情况下delete非常容易出问题,那是因为有可能你要删除的这个对象在Qt的事件循环里还排队,但你却已经在外面删除了它,这样程序会发生崩溃。所以需要 善用QObject::deleteLater 和 QObject::destroyed来进行内存管理。如上面实例使用到的代码:
//释放堆空间资源 connect(m_th, &QThread::finished, m_obj, &QObject::deleteLater); connect(m_th, &QThread::finished, m_th, &QObject::deleteLater); //设置野指针为nullptr connect(m_th, &QObject::destroyed, this, &MainWindow::SetPtrNullptr); connect(m_obj, &QObject::destroyed, this, &MainWindow::SetPtrNullptr); //消除野指针 void SetPtrNullptr(QObject *sender){ if(qobject_cast<QObject*>(m_th) == sender){ m_th = nullptr; qDebug("set m_th = nullptr"); } if(qobject_cast<QObject*>(m_obj) == sender){ m_obj = nullptr; qDebug("set m_obj = nullptr"); } }当我们调用线程的 quit 或者 exit 函数,并且线程到达了事件循环的状态,那么线程就会在结束并且发出 QThread::finished 的信号来触发 QObject::deleteLater 槽函数,QObject::deleteLater就会销毁系统为m_obj、m_th对象分配的资源。这个时候m_obj、m_th指针就属于野指针了,所以需要根据QObject类或者QObject派生类对象销毁时发出来的 QObject::destroyed 信号来设置m_obj、m_th指针为nullptr,避免野指针的存在。
运行上面的实例,然后点击【exit】按钮,结果如下图:
这种QT多线程的方法,实现简单、使用灵活,并且思路清晰,相对继承于QThread类的方式更有可靠性,这种方法也是官方推荐的实现方法。如果线程要用到事件循环,使用继承QObject的多线程方法无疑是一个更好的选择;
创建QObject派生类对象不能带有父类;
调用QThread::start是默认启动事件循环;
必须需要使用信号槽的方式使用线程;
需要注意跨线资源的创建,例如QTimer、QUdpSocket等资源,如果需要在子线程中使用,必须要在子线程创建;
要善用QObject::deleteLater 和 QObject::destroyed来进行内存管理 ;
尽量避免使用terminate强制退出线程,若需要退出线程,可以使用quit或exit;