QThread内创建QUDPSocket接收并处理数据

本文探讨了如何在Qt中使用QThread避免主线程阻塞,通过QUDPSocket在子线程接收和解析数据,并介绍了connect函数的不同连接模式。重点讲解了直连模式与队列模式的应用,以及如何通过创建QObject派生类并移入QThread处理复杂业务逻辑,实现信号槽的自动队列模式执行。
摘要由CSDN通过智能技术生成

前言

最近做项目,与仿真机通信。仿真机发送数据频率为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的方式暂不介绍,有时间再整理。

Qt中,可以使用QThread类来实现多线程接收图像数据。以下是一个简单的例子: 1. 创建一个继承自QThread的类,如下所示: ``` class ImageReceiver : public QThread { Q_OBJECT public: explicit ImageReceiver(QObject *parent = nullptr); signals: void imageReceived(QImage image); protected: void run() override; private: // 定义接收图像数据的方法 }; ``` 2. 实现run()方法,在该方法中实现接收图像数据的逻辑。可以使用Qt的网络类(如QTcpSocket、QUdpSocket等)来接收数据,也可以使用第三方库(如OpenCV)来实现。 ``` void ImageReceiver::run() { // 接收图像数据的逻辑 // 例如使用QTcpSocket接收数据 QTcpSocket socket; socket.connectToHost("127.0.0.1", 8000); if (socket.waitForConnected()) { while (true) { // 读取数据 QByteArray data = socket.readAll(); // 将数据转换为QImage对象 QImage image = QImage::fromData(data); // 发送信号,通知主线程接收到了图像数据 emit imageReceived(image); } } } ``` 3. 在主线程中创建ImageReceiver对象,并连接信号和槽,如下所示: ``` ImageReceiver *receiver = new ImageReceiver(this); connect(receiver, &ImageReceiver::imageReceived, [=](QImage image) { // 在主线程中处理接收到的图像数据 // 例如显示图像 ui->label->setPixmap(QPixmap::fromImage(image)); }); receiver->start(); ``` 在上述代码中,我们在主线程中创建了一个ImageReceiver对象,并连接了imageReceived信号和一个Lambda表达式槽。当ImageReceiver对象接收到图像数据时,会发送imageReceived信号,主线程中的Lambda表达式槽会被触发,处理接收到的图像数据,并将其显示在界面上。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值