QSharedPointer shared across QThread
-
I am having trouble using QSharedPointer accross threads. Unless I misunderstood the documentation, I should be able to copy a QSharedPointer instance between threads through signals/slots.
The situation is as follows: I have a device scanner which runs in a separate thread, in the background. When a device is detected, it should pass the instance to the main backend class through a signal / slot connection, and not own this device anymore. I could even delete the device scanner instance to stop scanning, in a RAII fashion.
So QSharedPointer looked like a good candidate: I could safely pass any copies multiple times and safely reset the QSharedPointer instance in the device scanner, or have the device scanner deleted. The device would be released when the QSharedPointer reference count goes to zero, i.e. when it is not used anywhere anymore.
Here is a minimal example of the architecture I am working with at the moment:
#include <QCoreApplication> #include <QSharedPointer> #include <QThread> #include <QTimer> class Device : public QObject { Q_OBJECT public: explicit Device(QObject* parent = nullptr) : QObject(parent) { qDebug() << "created device" << this; } ~Device() { qDebug() << "destroyed device" << this; } }; class DeviceScanner : public QObject { Q_OBJECT public: explicit DeviceScanner(QObject* parent = nullptr) : QObject(parent) { } // simulate the detection of a device void detectDevice() { QSharedPointer<Device> device(new Device(nullptr)); qDebug() << "detected device" << device.get(); emit detected(device); } signals: void detected(QSharedPointer<Device> device); }; class DeviceHandler : public QObject { Q_OBJECT public: explicit DeviceHandler(QObject* parent = nullptr) : QObject(parent) { m_deviceScanner = new DeviceScanner(nullptr); // toggle this line // m_deviceScanner->moveToThread(&m_deviceScannerThread); m_deviceScannerThread.start(); QObject::connect(&m_deviceScannerThread, &QThread::finished, m_deviceScanner, &QObject::deleteLater); QObject::connect(this, &DeviceHandler::triggerDetectDevice, m_deviceScanner, &DeviceScanner::detectDevice); QObject::connect(m_deviceScanner, &DeviceScanner::detected, this, &DeviceHandler::detected); } ~DeviceHandler() { m_deviceScannerThread.quit(); m_deviceScannerThread.wait(); } signals: void triggerDetectDevice(); void detected(QSharedPointer<Device> device); private: DeviceScanner* m_deviceScanner; QThread m_deviceScannerThread; }; int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); DeviceHandler* dh = new DeviceHandler(nullptr); QObject::connect(dh, &DeviceHandler::detected, dh, [dh, &a](QSharedPointer<Device> device) { dh->deleteLater(); // only needed to scan a single device, not needed anymore qDebug() << "ready to work with detected device" << device.get(); QTimer::singleShot(100, device.get(), [device, &a]() { qDebug() << "device is still alive" << device.get(); }); QTimer::singleShot(1000, &a, &QCoreApplication::quit); }); emit dh->triggerDetectDevice(); return a.exec(); }
When DeviceScanner is not moved to the QThread, here is the output:
created device Device(0x123456) detected device Device(0x123456) ready to work with detected device Device(0x123456) device is still alive Device(0x123456) destroyed device Device(0x123456)
I.e. what I would expect.
When I am moving the DeviceScanner instance to the dedicated QThread, here is what happens:
created device Device(0x123456) detected device Device(0x123456) ready to work with detected device Device(0x123456)
I.e. the "device is still alive" part does not happen. Also the memory in the QSharedPointer is not freed.
What am I doing wrong here?
If QSharedPointer is expected not work in this case, what would be my options?Thank you for your help in advance.
EDIT: If that helps, I am using Qt 6.4.3 on Windows, MinGW 64-bit.
-
That's the problem:QObject::connect(dh, &DeviceHandler::detected, dh, ...);
The lambda is destroyed when dh gets destroyed because you passed dh as third argument./edit: The problem is the thread affinity of QObjects. When a QObject is created, the current thread is used as the thread where the object lives in. Therefore QTimer::singleShot() also places the event in this event loop but since the event loop gets destroyed it will never be delivered.
-->// simulate the detection of a device void detectDevice() { QSharedPointer<Device> device(new Device(nullptr)); device->moveToThread(qApp->thread()); qDebug() << "detected device" << QThread::currentThreadId(); emit detected(device); }
-
@Christian-Ehrlicher I see, so I figure I have two options here:
- stop using the QThread, but that is not my favored option because I would like the device scanning to happen reliably even when the GUI thread was frozen (that was the reason why I did this move in the first place). I could also create the QThread later
- move the Device to the "main" thread before it is passed to
DeviceHandler::detected
. Trying this out it seems to work, and it matches "moving the ownership" of the Device instance to the rest of the app.
Marking this as solved, thank you for your help!
-