QQmlApplicationEngine engine;
FD_UDP* udpWorker = new FD_UDP();
QThread* udpThread = new QThread();
udpWorker->moveToThread(udpThread);
// 当线程启动时,调用udpWorker的receiveDataFormXP()函数
QObject::connect(udpThread, &QThread::started, udpWorker, &FD_UDP::receiveDataFormXP);
udpThread->start();
//expose the positon data to qml
engine.rootContext()->setContextProperty(“udpWorker”,udpWorker);
子线程利用udp接收数据存储起来,在qml中实时使用这些数据,二者有线程上的冲突.
当我试图在ui界面,实时适用子线程中的数据时,如
Text {
id: position
text: (udpWorker.targetDataQML.length > 1)
? "Longitude: " + udpWorker.targetDataQML[0] + ", Latitude: " + udpWorker.targetDataQML[1]
: "Waiting for position data..."
x: 0
y: 0
color: "blue"
}
会报错如下:
当前的代码中,将 udpWorker
移动到子线程以处理 UDP 数据接收,但将其暴露给 QML 引擎 (engine.rootContext()->setContextProperty("udpWorker", udpWorker)
) 会导致跨线程访问问题,因为 QML 引擎和 UDP worker 位于不同的线程。
这是一个典型的多线程和 UI 交互的问题,QML 和 UI 组件只能在主线程中运行,而当前是直接在子线程中操作数据,并通过 setContextProperty
直接暴露了 udpWorker
。这种情况下,如果 QML 试图访问子线程的数据,就会产生线程冲突(正如错误提示)。
解决方案
方法一:
信号与槽方法:
-
数据处理逻辑保持在子线程中,UI 更新在主线程:你可以让
udpWorker
在子线程中接收数据,但通过信号与槽机制将数据传递给主线程,再由主线程负责更新 QML。 -
使用
QueuedConnection
机制确保线程安全:Qt 的信号槽机制提供了Qt::QueuedConnection
,它可以确保跨线程的信号传递是在接收线程(主线程)处理的。
修改后的代码结构
可以按照如下步骤修改你的代码:
- 子线程接收数据,发送信号给主线程:当子线程接收到数据时,发出一个信号,传递数据给主线程处理。
- 主线程更新 QML:在主线程中接收信号,然后更新 UI 或 QML 属性。
代码实现
1. 修改 FD_UDP
类,添加信号:
class FD_UDP : public QObject {
Q_OBJECT
public:
explicit FD_UDP(QObject *parent = nullptr) : QObject(parent) {}
public slots:
void receiveDataFormXP() {
// 子线程中接收数据逻辑
// 假设接收到的经纬度数据是 lat 和 lon
double lat = ...; // 接收到的纬度数据
double lon = ...; // 接收到的经度数据
// 发出信号,传递数据到主线程
emit positionDataReceived(lat, lon);
}
signals:
void positionDataReceived(double lat, double lon); // 信号,将数据传递给主线程
};
2. 在主线程中接收数据并更新 QML
在主线程中连接信号并更新数据:
// 创建 UDP worker 和线程
FD_UDP* udpWorker = new FD_UDP();
QThread* udpThread = new QThread();
udpWorker->moveToThread(udpThread);
// 当线程启动时,调用 udpWorker 的 receiveDataFormXP() 函数
QObject::connect(udpThread, &QThread::started, udpWorker, &FD_UDP::receiveDataFormXP);
// 当接收到数据时,将数据传递给主线程
QObject::connect(udpWorker, &FD_UDP::positionDataReceived, [&](double lat, double lon) {
// 通过 QML 引擎更新经纬度数据
engine.rootContext()->setContextProperty("longitude", lat);
engine.rootContext()->setContextProperty("latitude", lon);
}, Qt::QueuedConnection);
// 启动线程
udpThread->start();
3. 在 QML 中使用 latitude
和 longitude
你可以在 QML 中使用暴露的 latitude
和 longitude
数据进行显示:
Text {
id: position
text: "Longitude: " + longitude + ", Latitude: " + latitude
x: 0
y: 0
color: "blue"
}
说明:
- 信号与槽机制:这里使用了
QObject::connect
,将FD_UDP::positionDataReceived
信号连接到主线程中的一个 lambda 函数。在该函数中,我们通过setContextProperty
将latitude
和longitude
更新到 QML 上。 Qt::QueuedConnection
:我们指定了Qt::QueuedConnection
,确保槽函数在主线程的事件循环中执行,从而避免跨线程访问引发的错误。- 更新 QML 属性:每次接收到新的经纬度数据时,都会更新 QML 中的
longitude
和latitude
,确保数据是实时显示的。
方法二
使用线程安全的数据结构
如果你需要在主线程和子线程之间共享数据,可以使用线程安全的数据结构来存储和访问数据。例如,使用 QMutex
来保护数据访问:
注意要将qml使用的数据使用property注册,并且创建相应的getter和writer。
-
定义线程安全的数据结构:
class SharedData : public QObject { Q_OBJECT public: explicit SharedData(QObject* parent = nullptr) : QObject(parent) {} double longitude; double latitude; double true_airspeed; double heading; QMutex mutex; // 互斥锁 };
-
在子线程中更新数据:
void FD_UDP::receiveDataFromXP() { while (running) { for (int attempt = 0; attempt <= RETRY_ATTEMPTS; ++attempt) { int result = getDREFs(sock, drefs, results, 4, sizes); if (result >= 0) { double lon = results[0][0]; double lat = results[1][0]; double true_airspeed = results[2][0]; double heading = results[3][0]; QMutexLocker locker(&sharedData->mutex); // 锁定互斥锁 sharedData->longitude = lon; sharedData->latitude = lat; sharedData->true_airspeed = true_airspeed; sharedData->heading = heading; locker.unlock(); break; // 退出重试循环 } } } }
-
在主线程中访问数据:
// 在主线程中访问共享数据 QMutexLocker locker(&sharedData->mutex); // 锁定互斥锁 double lon = sharedData->longitude; double lat = sharedData->latitude; double true_airspeed = sharedData->true_airspeed; double heading = sharedData->heading; locker.unlock();
sharedData初始化
sharedData
需要在主线程中初始化,并在子线程中访问。你可以在主线程中创建一个 SharedData
实例,并将其传递给子线程,以便子线程能够访问和更新共享数据。以下是如何初始化 sharedData
并在主线程和子线程之间进行传递的详细步骤:
1. 定义 SharedData
类
首先,定义 SharedData
类,该类包含需要共享的数据和一个互斥锁以确保线程安全。
class SharedData : public QObject {
Q_OBJECT
public:
explicit SharedData(QObject* parent = nullptr) : QObject(parent) {}
double longitude;
double latitude;
double true_airspeed;
double heading;
QMutex mutex; // 互斥锁,保护数据
};
2. 在主线程中初始化 SharedData
在主线程中创建一个 SharedData
实例,并将其传递给子线程中的工作对象。
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// 创建共享数据实例
SharedData* sharedData = new SharedData();
// 创建工作对象和线程
FD_UDP* udpWorker = new FD_UDP();
QThread* udpThread = new QThread();
// 将工作对象移动到子线程
udpWorker->moveToThread(udpThread);
// 将共享数据传递给工作对象
udpWorker->setSharedData(sharedData);
// 连接信号和槽
QObject::connect(udpThread, &QThread::started, udpWorker, &FD_UDP::receiveDataFromXP);
udpThread->start();
// 在 QML 引擎中设置共享数据
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("sharedData", sharedData);
return app.exec();
}
3. 在 FD_UDP
中使用 SharedData
在 FD_UDP
类中,你需要添加一个方法来设置 SharedData
的指针,并在子线程中使用它。
class FD_UDP : public QObject {
Q_OBJECT
public:
explicit FD_UDP(QObject* parent = nullptr) : QObject(parent), sharedData(nullptr) {}
void setSharedData(SharedData* data) {
sharedData = data;
}
public slots:
void receiveDataFromXP() {
while (running) {
for (int attempt = 0; attempt <= RETRY_ATTEMPTS; ++attempt) {
int result = getDREFs(sock, drefs, results, 4, sizes);
if (result >= 0) {
double lon = results[0][0];
double lat = results[1][0];
double true_airspeed = results[2][0];
double heading = results[3][0];
// 更新共享数据
if (sharedData) {
QMutexLocker locker(&sharedData->mutex);
sharedData->longitude = lon;
sharedData->latitude = lat;
sharedData->true_airspeed = true_airspeed;
sharedData->heading = heading;
}
break; // 退出重试循环
}
}
}
}
private:
SharedData* sharedData;
};
4. 在 QML 中使用 SharedData
在 QML 文件中,你可以直接访问 SharedData
对象中的属性。
import QtQuick 2.15
import QtQuick.Controls 2.15
Rectangle {
width: 640
height: 480
Text {
id: lonText
text: "Longitude: " + sharedData.longitude
anchors.centerIn: parent
}
Text {
id: latText
text: "Latitude: " + sharedData.latitude
anchors.top: lonText.bottom
anchors.horizontalCenter: parent.horizontalCenter
}
// 其他 UI 元素
}
- 在主线程中:初始化
SharedData
实例,并将其传递给子线程中的工作对象。 - 在子线程中:使用
SharedData
实例来更新数据。 - 在主线程中:将
SharedData
实例传递到 QML 中,并在 QML 文件中使用它来显示数据。
通过这种方式,你可以确保 SharedData
在主线程和子线程之间安全地共享和更新数据。
总结
这样做可以确保子线程负责数据接收,主线程负责 UI 更新,从而避免线程冲突的错误。