解决Qt多线程中fromRawData函数生成的QByteArray数据不一致问题

解决Qt多线程中fromRawData函数生成的QByteArray数据不一致问题

icon


🔔 问题背景

在开发一个使用Qt框架的多线程应用程序时,我们遇到了一个棘手的问题:在不同线程中打印同一个QByteArray对象的内容时,得到了不一致的结果。这个问题出现在一个设备通信类中,该类使用一个工作线程来读取设备数据,然后通过信号-槽机制将数据传递到主线程进行处理。



📄 问题代码

代码结构如下:

class DeviceManager : public QObject {
public:
    DeviceManager() : QObject() {
        m_worker = new Worker();
        m_workerThread = new QThread(this);
        m_worker->moveToThread(m_workerThread);
        connect(m_workerThread, &QThread::started, m_worker, &Worker::readDevice);
        connect(m_worker, &Worker::dataReady, this, &DeviceManager::processData);
        m_workerThread->start();
    }
    ~DeviceManager() {
    	m_hidWork->breakFlag = false;
    	m_hidWorkThread->quit();
    	m_hidWorkThread->wait();
    	m_hidWorkThread->deleteLater();
    }

private:
    void processData(QByteArray data) {
        qDebug() << "Main thread: " << data.toHex(' ');
    }

    class Worker;
    Worker *m_worker;
    QThread *m_workerThread;
};

class DeviceManager::Worker : public QObject {
    Q_OBJECT
public:
    Worker(QObject *parent = nullptr) : QObject(parent) {}
	
	bool breakFlag = true;
	
    void readDevice() {
        auto buffer = new char[1024];
        while(breakFlag) {
        	size_t size = 5;
        	memset(buffer, 0, size)
        	// 这里是模拟读取设备数据
        	buffer = {0x01, 0x02, 0x03, 0x04, 0x05};
        	QByteArray deviceData = QByteArray::fromRawData(reinterpret_cast<char*>(buffer), size);
        	qDebug() << "Worker thread: " << deviceData.toHex(' ');
        	emit dataReady(QByteArray(deviceData));
        }
    }

signals:
    void dataReady(QByteArray data);
};


❓ 问题描述

在运行这段代码时,当主线程进行了一个UI阻塞操作的时候,我们观察会到工作线程和主线程中打印的QByteArray内容不一致。例如:

Worker thread: 01 02 03 04 05
Main thread: 00 00 00 00 00

这显然不是我们期望的结果,因为两个线程应该打印相同的数据。



🩺 问题分析

经过仔细分析,我们发现问题的根源在于QByteArray::fromRawData()的使用方式。这个函数创建了一个共享底层数据QByteArray,而不是复制数据。这意味着:

  • fromRawData()创建的QByteArray与原始缓冲区共享数据
  • 当原始缓冲区被修改或释放时,QByteArray的内容可能变得无效
  • 在跨线程传递时,如果原始数据发生变化,接收线程可能会得到意外的结果


✔ 解决方案

解决这个问题的关键是确保在发送信号时创建QByteArray完整副本。以下是修改后的Worker::readDevice()方法:

void Worker::readDevice() {
	auto buffer = new char[1024];
    while(breakFlag) {
       	size_t size = 5;
       	memset(buffer, 0, size)
       	// 这里是模拟读取设备数据
       	buffer = {0x01, 0x02, 0x03, 0x04, 0x05};
       	//QByteArray deviceData = QByteArray::fromRawData(reinterpret_cast<char*>(buffer), size);
	    QByteArray deviceData((reinterpret_cast<char*>(buffer), size);  // 创建数据的副本
	    qDebug() << "Worker thread: " << deviceData.toHex(' ');
	    emit dataReady(deviceData);
	}	    
}

这里的改动看似很小,但却解决了问题:

  • 我们使用QByteArray(const char *data, int size)构造函数来创建QByteArray
  • 这个构造函数会复制提供的数据,而不是共享它。
  • 结果是一个独立的QByteArray对象,其内容不会受到原始缓冲区变化的影响。


  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
QT多线程使用QTcpSocket需要注意以下几个问题: 1. 多线程操作同一个QTcpSocket对象会导致数据错乱。每个线程应该有自己的QTcpSocket对象来读写数据。 2. 在多线程,需要使用信号和槽机制进行线程间通信。当数据准备好时,发送一个信号通知主线程或其他线程进行处理。 3. 在QTcpSocket的readyRead()信号,应该先获取可读数据的大小,然后再读取数据,避免读取不完整的数据。在读取数据之前,可以使用waitForReadyRead()函数等待数据的到来。 4. 在QTcpSocket的write()函数,应该保证一次性写入所有数据,避免发送不完整的数据。如果数据过大,可以分段发送。 下面是一个简单的示例代码: ```cpp // 多线程读取数据 class TcpThread : public QThread { Q_OBJECT public: explicit TcpThread(QObject *parent = 0); void run(); void setSocket(qintptr descriptor); signals: void error(QTcpSocket::SocketError socketError); void readyRead(QByteArray data); void disconnected(); private: QTcpSocket *socket; }; TcpThread::TcpThread(QObject *parent) : QThread(parent) { socket = new QTcpSocket(this); connect(socket, SIGNAL(readyRead()), this, SLOT(readData())); connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected())); } void TcpThread::run() { if (!socket->setSocketDescriptor(descriptor)) { emit error(socket->error()); return; } exec(); } void TcpThread::setSocket(qintptr descriptor) { this->descriptor = descriptor; } void TcpThread::readData() { QByteArray data; while (socket->bytesAvailable()) { int len = socket->bytesAvailable(); data.append(socket->read(len)); } emit readyRead(data); } void TcpThread::disconnected() { emit disconnected(); } // 主线程处理数据 class TcpServer : public QTcpServer { Q_OBJECT public: explicit TcpServer(QObject *parent = 0); void incomingConnection(qintptr socketDescriptor); void processData(QByteArray data); signals: void dataReady(QByteArray data); private: QList<TcpThread*> threads; }; TcpServer::TcpServer(QObject *parent) : QTcpServer(parent) { } void TcpServer::incomingConnection(qintptr socketDescriptor) { TcpThread *thread = new TcpThread(this); connect(thread, SIGNAL(readyRead(QByteArray)), this, SLOT(processData(QByteArray))); connect(thread, SIGNAL(disconnected()), thread, SLOT(deleteLater())); thread->setSocket(socketDescriptor); thread->start(); threads.append(thread); } void TcpServer::processData(QByteArray data) { emit dataReady(data); } // 使用示例 TcpServer server; connect(&server, SIGNAL(dataReady(QByteArray)), this, SLOT(processData(QByteArray))); server.listen(QHostAddress::Any, port); ``` 这个示例代码,创建了一个TcpThread类用于多线程读取数据,主线程通过信号和槽机制处理数据。在TcpThread类,使用了readyRead()信号和readData()槽函数来读取数据,并在读取完成后,通过readyRead()信号通知主线程处理数据。在主线程,使用incomingConnection()函数创建TcpThread对象,并通过dataReady()信号处理数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值