QThread内创建QUDPSocket接收并处理数据
前言
最近做项目,与仿真机通信。仿真机发送数据频率为1毫秒,导致Qt上位机在主线程频繁接收数据,造成界面卡死,因此将整个udp的通信和解析都放在线程中进行。
connect函数与QThread
connect函数最后一个参数指定了三种连接方式:自动模式、直连模式和队列模式。自动模式下,是使用的直连模式还是队列模式,主要看信号发出者和槽接收者是否在同一线程。
- 如果sender和reciver对象在同一线程中被创建,则采用直连模式。
- 如果sender和reciver对象在不同的对象中被创建,则采用队列模式。
直连模式: 是指当信号发送时,槽函数直接被调用,不管reciver是那个线程创建的,都在发射信号的线程内执行。换句话说,在子线程中创建sender对象,发送信号后,不管reciver是在主线程还是子线程创建,都会在子线程中执行。
直连模式下,emit信号之后,等槽函数返回之后继续执行。
#include <QtCore/QCoreApplication>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtCore/QDebug>
class Dummy:public QObject
{
Q_OBJECT
public:
Dummy(){}
public slots:
void emitsig()
{
emit sig();
}
signals:
void sig();
};
class Thread:public QThread
{
Q_OBJECT
public:
Thread(QObject* parent=0):QThread(parent)
{
//moveToThread(this);
}
public slots:
void slot_main()
{
qDebug()<<"slot_main function execute in thread:" <<currentThreadId();
}
protected:
void run()
{
qDebug()<<"run function execute in thread:"<<currentThreadId();
exec();
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"main thread:"<<QThread::currentThreadId();
Thread thread;
Dummy dummy;
QObject::connect(&dummy, SIGNAL(sig()), &thread, SLOT(slot_main()));
thread.start();
dummy.emitsig();
return a.exec();
}
上面代码中,dummy和thread分别作为sender和reciver,都在同一个线程创建,采用直连模式,直接调用,所以slot_main()在主线程中执行。结果如下:
main thread: 0x1a40
slot_main function execute in thread: 0x1a40
run function execute in thread: 0x1a48
注意: 若想让slot_main()在子线程中执行,只需要将thread move到子线程中,改变thread的所在线程,采用队列模式。即将上面代码中“movetoThread(this);”解开注释即可。这种将线程对象移动到子线程的方案是强烈不推荐的。
队列模式: 是指sender发送信号后,信号被存到信号队列中,程序继续执行。直到reciver所在的线程事件循环处理到该信号时,槽函数被调用。槽函数在reciver对象所在的线程内执行。
队列模式下,emit信号后继续执行,并不立刻执行槽函数。
#include <QtCore/QCoreApplication>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtCore/QDebug>
class Dummy:public QObject
{
Q_OBJECT
public:
Dummy(QObject* parent=0):QObject(parent){}
public slots:
void emitsig()
{
emit sig();
}
signals:
void sig();
};
class Thread:public QThread
{
Q_OBJECT
public:
Thread(QObject* parent=0):QThread(parent)
{
//moveToThread(this);
}
public slots:
void slot_thread()
{
qDebug()<<"from thread slot_thread:" <<currentThreadId();
}
signals:
void sig();
protected:
void run()
{
qDebug()<<"thread thread:"<<currentThreadId();
Dummy dummy;
connect(&dummy, SIGNAL(sig()), this, SLOT(slot_thread()));
dummy.emitsig();
exec();
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"main thread:"<<QThread::currentThreadId();
Thread thread;
thread.start();
return a.exec();
}
代码中,thread在主线程中创建,dummy在子线程中创建,采用的是队列模式,所以槽函数slot_thread()其实在主线程中运行。并不能解决主界面卡顿问题。解决方法有两个:
- 一是将thread move到子线程,sender和reciver在同一线程,该方案同样不推荐。
- 二是指定连接模式为直接模式,直连模式下,槽必然在sender所在的线程执行。只要做好sender对象和槽函数的线程同步。
推荐方式
定义一个普通的QObject派生类,然后将其对象move到QThread中。在主线程中创建sender,那么信号槽自动采用队列模式,槽函数就在子线程中运行了,因此这个派生类通常用于(在线程中)处理复杂的业务逻辑。
这样,使用信号和槽时根本不用考虑多线程的存在,也不用使用QMutex来进行同步,Qt的事件循环会自己自动处理好这个。
#include <QtCore/QCoreApplication>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtCore/QDebug>
class Dummy:public QObject
{
Q_OBJECT
public:
Dummy(QObject* parent=0):QObject(parent) {}
public slots:
void emitsig()
{
emit sig();
}
signals:
void sig();
};
class Object:public QObject
{
Q_OBJECT
public:
Object(){}
public slots:
void slot()
{
qDebug()<<"from thread slot:" <<QThread::currentThreadId();
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"main thread:"<<QThread::currentThreadId();
QThread thread;
Object obj;
Dummy dummy;
obj.moveToThread(&thread);
QObject::connect(&dummy, SIGNAL(sig()), &obj, SLOT(slot()));
thread.start();
dummy.emitsig();
return a.exec();
}
QThread内创建QUDPSocket接收并处理数据
在线程中创建UDPserver,接收数据并解析,解析完毕后,发送信号给主线程,进行界面更新。
子线程:
void HandleDatasThread::run()
{
qDebug()<<"zixiancheng:"<<QThread::currentThreadId();
QUdpSocket server; //用于接收数据
quit = false;
server.bind(port, QUdpSocket::ShareAddress);
while(!quit)
{
if (server.hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(int(server.pendingDatagramSize()));
server.readDatagram(datagram.data(), datagram.size());
qDebug()<<datagram.toHex();
parseRecvData(datagram);
emit updatetable();
}
}
}
主线程:
connect(&m_calculationThread,SIGNAL(updatetable()),this,SLOT(computingFinished()));
void MainWindow::computingFinished()
{
recvVars = m_calculationThread.getVars();
if(QDateTime::currentMSecsSinceEpoch() / 1000 -lasttime>2)
{
setTableWidget(ui->recvTableWidget, recvVars);
lasttime = QDateTime::currentMSecsSinceEpoch() / 1000;
}
}
子线程中发送信号,主线程中处理,因此是队列模式,槽函数会在主线程中运行,从而更新界面。
通过普通的Object,movetothread的方式暂不介绍,有时间再整理。