对qt多线程以及事件投递的理解
用QThread启动线程的良好方法以及理解。
1) 用原始的QThread的started信号触发自定义的slot启动线程,而不是派生QThread的类重载run函数启动线程。
将一个类派生自QObject,然后实现所有的signal/slot,然后通过调用movetothread函数来使他们执行在新的线程里面,而不是每次都要重新派生QThread,并且派生QThread的另外一个不好的地方是只有run函数内部的代码才会执行在新线程里面,相比起来,派生QObject并使用movetothread函数更具有灵活性。
2) 自定义对象moveToThread进线程后,事件循环可以完全在此线程中运行。而它的内存管理应该还属于主线程。
如下例子中(MyObject* myObj)被moveToThread进线程(QThread *thread)。假如对象(MyObject* myObj)有父亲,它不能移动这种关系。在另一个线程(而不是创建它的那个线程)中delete QObject对象是不安全的。除非你可以保证在同一时刻对象不在处理事件。可以用QObject::deleteLater(),它会投递一个DeferredDelete事件,这会被对象线程的事件循环最终选取到。
3)exec的重要性。
主线程开始它的事件循环需要调用QCoreApplication::exec(), 子线程需要用QThread::exec(),功能基本是一样的,当然qt内部肯定是不同的实现。
假如没有事件循环运行,事件不会分发给对象。举例来说,假如你在一个线程中创建了一个QTimer对象,但从没有调用过exec(),那么QTimer就不会发射它的timeout()信号.对deleteLater()也不会工作。(这同样适用于主线程)。
4) 子线程接收事件
a) 如下例子中,子线程并没有执行exec(),确切地说是QThread::exec(),那么其实我也可以把Qtrhead换成Qthread派生类,在run中执行事件循环。但是, 其实用主线程的exec(), 加上moveToThread,加上connect的时候使用Qt::QueuedConnection就可以让slot在子线程中运行,那么在子线程exec也没多大意义了。
b)本例子真正使用的是QCoreApplication::postEvent,和重载QObject类的customEvent的方法。消息一样会在(MyObject* myObj)所在的线程处理。
5) 关于QCoreApplication::postEvent和QCoreApplication::sendEvent
postEvent: 可以给别的线程发送事件。事件会在目的对象所属的线程中运行。这是一个异步接口。 sendEvent: 仅用于同一个线程之间发送事件。目的对象必须与当前线程一样。这是一个同步接口。假如发送给属于另一个线程的对象,会报错:ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread a51f48. Receiver ‘‘ (of type ‘MyObject‘) was created in thread a3bf18", file kernel\qcoreapplication.cpp, line 539
以下是结合上述描述的3个文件。可正常编译运行。
-------------------------------------------------------------------------------------------------
maim.cpp
-------------------------------------------------------------------------------------------------
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QObject>
#include <QTimer>
#include "myobject.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main:" << (qlonglong)QThread::currentThreadId();
//! 业务逻辑对象
MyObject* myObj = new MyObject;
//! 线程
QThread *thread = new QThread();
//! 定时器
QTimer *timer = new QTimer();
//! 把业务逻辑对象设置进线程,不要把Qthread放进自己线程,这样的写法不建议。
myObj->moveToThread(thread);
//! 线程启动
thread->start();
//! 绑定的时候最好设置QueuedConnection。这样就允许在myObj线程执行,否则可能会在主线程执行。
//! 如果希望不要再子线程执行,用DirectConnection。
QObject::connect(thread, SIGNAL(started()), myObj, SLOT(Started()), Qt::QueuedConnection);
QObject::connect(timer, SIGNAL(timeout()), myObj, SLOT(Timeout()), Qt::QueuedConnection);
//! 定时器启动
timer->start(1000);
//! 发送事件
QCoreApplication::postEvent(myObj, new QEvent(CustomEvent_Login));
qDebug() << QString("[%1]send the event: %2!")
.arg((qlonglong)QThread::currentThreadId())
.arg(CustomEvent_Login);
//! MyObject::Started/MyObject::Timeout/都会在同一个子线程调用。
return a.exec();
}
-------------------------------------------------------------------------------------------------
myobject.cpp
-------------------------------------------------------------------------------------------------
#include "myobject.h"
#include <QDebug>
#include <QThread>
MyObject::MyObject()
{
}
void MyObject::Started()
{
qDebug() << "Started:" << (qlonglong)QThread::currentThreadId() ;
}
void MyObject::Timeout()
{
qDebug() << "Timeout:" << (qlonglong)QThread::currentThreadId() ;
}
void MyObject::customEvent(QEvent *e)
{
if (e->type() == CustomEvent_Login) //捕获消息
{
qDebug() << QString("[%1]catch the event: %2!")
.arg((qlonglong)QThread::currentThreadId())
.arg(e->type());
}
}
-------------------------------------------------------------------------------------------------
myobject.h
-------------------------------------------------------------------------------------------------
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <QObject>
#include <QEvent>
const QEvent::Type CustomEvent_Login = (QEvent::Type)5001; //! 建议用5000以上唯一的标识
class MyObject : public QObject
{
Q_OBJECT
public:
MyObject();
protected slots:
void Started();
void Timeout();
private:
void customEvent(QEvent *e); //! 重载QObject的续虚函数
};
#endif // MYOBJECT_H
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。