Stopping and deleting background QThread and its worker
-
I have UDPListener class (derived from QObject) that is a worker run in a background QThread.
It is standard (as far as I know) way of doing it:
I'm constructing parent-less UDPListner object in main thread.
I'm constructing parent-less QThread object in main thread.
I'm connecting QThread::started to UDPListener::run.
Also I'm connecting UDPListener signals to my own slots on main thread objects that will handle received data. I think it doesn't matter here.
I'm moving the UDPListener to the QThread (with QObject::moveToThread).
I'm starting the thread (with QThread::start).
The UDPListener listens to multicasts and propagates received data to the main thread. There is no end to this. It does so for the life time of the application.
I noticed that the UDPListener object is never destroyed. Also QThread is not destroyed. If I make some main thread object a parent of the QThread then it is destroyed but emits a warning on output that QThread was destroyed while still runing. And sometimes causes failures.
So now during destruction of the main thread object "owning" the QThread and UDPListener I'm calling QThread::quit on the background thread. It does quit the thread (it is visible on debugger output that thread exited). But still neither QThread nor UDPListener are deleted.
So I connected deleteLater on both of them to QThread::finished. But it doesn't delete neither of those object. It seems UDPListener is not deleted since the thread in which it exists (the background thread) already exited its events loop (by the call to quit). And same issue with QThread since the "owning" main thread object is destroyed as a child of QApplication which is destroyed by leaving main (as its local variable).
So how should I assure that the background thread quits properly and then both the UDPListener that lived in it and the thread itself are deleted (destroyed)?
-
Hi
to destroy Worker and Thread you can use deleteLater slot ando connect it to finished signal
@
connect(thread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
@In Qt5 and above you can also write
@
connect(thread, &QThread::finished, worker, &Worker::deleteLater);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
@ -
[quote author="mcosta" date="1364380199"]to destroy Worker and Thread you can use deleteLater slot ando connect it to finished signal[/quote]
As I described already it doesn't work in my scenario.
It doesn't work because QThread doesn't finish due to internal reasons but on external request. By itself my thread will never exit (and it should be so since it has to monitor network). During application exit I'm closing it with a call to QThread::quit. But this means that finished is emitted when the thread's events loop already shut down and will not process the deleteLater on the Worker (UDPListener in my case).
Very similar reasons lead to QThread not being deleted. Its deleteLater should be done in main thread (since QThread is main thread object as opposed to UDPListener). But since I call QThread::quit due (indirectly) to QApplication destruction the main thread events loop also already shut down and will not process the deleteLater for QThread.
-
What version of Qt are you using? From Qt 4.8 onwards, deleteLater() can be connected to QThread::finished(), and the deletion will happen when the thread's event loop stops. This doesn't happen for Qt 4.7 and earlier, however.
-
Qt5
Yet it does not happen. I both added qDebug (in my UDPListener run on the thread) and set a breakpoint in destructors (both UDPListener and QThread).
-
Bellow is the simplified version of the code which still contains the important aspects and shows the issues.
@#include <QtCore/QByteArray>
#include <QtCore/QDebug>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtCore/QtGlobal>
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QNetworkInterface>
#include <QtNetwork/QUdpSocket>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>// Required to use QHostAddress as parameter in signals.
Q_DECLARE_METATYPE( QHostAddress )////////////////////////////////////////////////////////////////////////////////
class UDPListener
: public QObject
{
Q_OBJECTpublic:
UDPListener( QObject* parent = nullptr )
: QObject( parent ),
theSocketPtr( new QUdpSocket( this ) )
{
// Required to use QHostAddress as parameter in signals.
qRegisterMetaType< QHostAddress >();if ( !theSocketPtr->bind( QHostAddress::AnyIPv4, 21212, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint ) ) { Q_ASSERT( false ); } Q_ASSERT( theSocketPtr->state() == QAbstractSocket::BoundState ); if ( !theSocketPtr->joinMulticastGroup( QHostAddress( "224.0.0.66" ) ) ) { Q_ASSERT( false ); }
}
virtual ~UDPListener()
{
qDebug() << "UDPListener::~UDPListener()";
}public slots:
void run()
{
connect(
theSocketPtr,
&QUdpSocket::readyRead,
this,
&UDPListener::processDatagrams
);processDatagrams();
}
private slots:
void processDatagram()
{
QByteArray datagram;
QHostAddress senderAddres;
quint16 senderPort;datagram.resize( theSocketPtr->pendingDatagramSize() ); theSocketPtr->readDatagram( datagram.data(), datagram.size(), &senderAddres, &senderPort ); emit datagramReceived( datagram, senderAddres, senderPort );
}
void processDatagrams()
{
while ( theSocketPtr->hasPendingDatagrams() )
processDatagram();
}signals:
void datagramReceived(
const QByteArray& aDatagram,
const QHostAddress& aSenderAddress,
quint16 aSenderPort
);private:
QUdpSocket* theSocketPtr;};
////////////////////////////////////////////////////////////////////////////////
class NetworkWatcher
: public QObject
{
Q_OBJECTpublic:
NetworkWatcher( QObject* parent = nullptr )
: QObject( parent ),
theUDPListenerThreadPtr(),
theUDPListenerPtr()
{
}virtual ~NetworkWatcher()
{
qDebug() << "NetworkWatcher::~NetworkWatcher()";theUDPListenerThreadPtr->quit();
}
void runOnNewThread()
{
theUDPListenerPtr = new UDPListener();
connect(
theUDPListenerPtr,
&UDPListener::datagramReceived,
this,
&NetworkWatcher::onDatagramReceived
);theUDPListenerThreadPtr = new QThread(); theUDPListenerThreadPtr->setObjectName( "NetworkWatcher::UDPListener" ); connect( theUDPListenerThreadPtr, &QThread::started, theUDPListenerPtr, &UDPListener::run ); connect( theUDPListenerThreadPtr, &QThread::finished, theUDPListenerPtr, &UDPListener::deleteLater ); connect( theUDPListenerThreadPtr, &QThread::finished, theUDPListenerThreadPtr, &QThread::deleteLater ); theUDPListenerPtr->moveToThread( theUDPListenerThreadPtr ); theUDPListenerThreadPtr->start();
}
protected slots:
void onDatagramReceived(
const QByteArray& aDatagram,
const QHostAddress& aSenderAddress,
quint16 aSenderPort
)
{
/* ... */
}private:
QThread* theUDPListenerThreadPtr;UDPListener* theUDPListenerPtr;
};
////////////////////////////////////////////////////////////////////////////////
class Application
: public QApplication
{
Q_OBJECTpublic:
Application( int& argc, char** argv )
: QApplication( argc, argv ),
theNetworkWatcherPtr( new NetworkWatcher( this ) )
{
theNetworkWatcherPtr->runOnNewThread();
}virtual ~Application()
{
qDebug() << "Application::~Application()";
}private:
NetworkWatcher* theNetworkWatcherPtr;};
////////////////////////////////////////////////////////////////////////////////
int main( int argc, char* argv[] )
{
Application application( argc, argv );QMainWindow mainWindow;
mainWindow.show();return application.exec();
}@Running the code shows that QThread (NetworkWatchar::theUDPListenerThreadPtr) and UDPListener (NetworkWatcher::theUDPListenerPtr) are never destroyed (nor their memory is deallocated).
-
Simplified code?
I think it's a little bit complicated.What means object never destroyed? Destuctor are never called?
I see that in your code you never call QThread::quit(); you should do.
I suggest to launch the Thread in MainWindow constructor or similar and call QThread::quit() on destructor or in "closeEvent" -
I would add one more connect statement and add a slot on your UDPListener to stop him working.
@
void runOnNewThread()
{
.
.
.
connect(
theUDPListenerPtr,
&UDPListener::finished,
theUDPListenerThreadPtr ,
&QThread::quit
);
.
.
.
@In this slot stop() (or whatever) you can set a boolean flag to true, and then change the function UDPListener::processDatagrams to look like this:
@
void UDPListener::stop()
{
m_bStopWorking = true;
}void UDPListener::processDatagrams()
{
while(theSocketPtr->hasPendingDatagrams())
{
if(!m_bStopWorking)
{
processDatagram();
}
else
{
emit finished();
}
}
}
@The while loop might be a problem though, because as long as you're in that loop control will not return to the event queue and therefor the stop() slot will not be called. I'm not sure how long your object usually spends in that loop. If it spends a lot of time in that loop you might have to think of a way to replace that loop by a signal slot connection. Maybe by connecting your datagramReceived(...) signal to your processDatagrams() slot?
-
[quote author="mcosta" date="1364427839"]What means object never destroyed? Destuctor are never called?[/quote]
Exactly. Destructors for UDPListener and QThread are never called.
[quote author="mcosta" date="1364427839"]I see that in your code you never call QThread::quit(); you should do.[/quote]
It does call QThread::quit. In NetworkWatcher destructor (invoked while destroying Application due to parent-child relationship). QThread::finished is emitted (checkable in debugger). And the thread does quit (seen in debugger). But connected deleteLater are not called!
I suspect it is because the events loop of main thread and the QThread are already shutdown. But I might be wrong.
[quote author="mcosta" date="1364427839"]I suggest to launch the Thread in MainWindow constructor or similar and call QThread::quit() on destructor or in "closeEvent"[/quote]
Calling QThread::quit from QMainWindow::closeEvent should help (if my above guess on the reasons of issues) with destroying and deleting QThread which is owned by the main thread. However it would not help with UDPListener since same conditions still exist for it. But it doesn't go well with the design since NetworkWatcher is bound to application, not some GUI component...
I will check this (to confirm that UDPListener will still not be destroyed) in few days since I'm on holidays now.
-
[quote author="KA51O" date="1364456687"]I would add one more connect statement and add a slot on your UDPListener to stop him working.[/quote]
Interesting. But who is to call it and when? Or should I call that function instead of calling quit in the NetworkWatcher destructor? That might actually work. But I will be able to check this in few days since I'm on holidays now.
[quote author="KA51O" date="1364456687"]The while loop might be a problem though, because as long as you're in that loop control will not return to the event queue and therefor the stop() slot will not be called. I'm not sure how long your object usually spends in that loop. If it spends a lot of time in that loop you might have to think of a way to replace that loop by a signal slot connection. Maybe by connecting your datagramReceived(...) signal to your processDatagrams() slot?[/quote]
Yeah... I don't like that loop at all. "The other thread":http://qt-project.org/forums/viewthread/26147/#119546 is all about that loop and getting rid of it. But I think that in my actual case this will likely not be a real issue since the datagrams are not frequent enough. (At least in developer's environment, not sure how about client's environment.)
-
[quote author="Adam Badura" date="1364460931"][quote author="KA51O" date="1364456687"]I would add one more connect statement and add a slot on your UDPListener to stop him working.[/quote]
Interesting. But who is to call it and when? Or should I call that function instead of calling quit in the NetworkWatcher destructor? That might actually work. But I will be able to check this in few days since I'm on holidays now.
[quote author="KA51O" date="1364456687"]The while loop might be a problem though, because as long as you're in that loop control will not return to the event queue and therefor the stop() slot will not be called. I'm not sure how long your object usually spends in that loop. If it spends a lot of time in that loop you might have to think of a way to replace that loop by a signal slot connection. Maybe by connecting your datagramReceived(...) signal to your processDatagrams() slot?[/quote]
Yeah... I don't like that loop at all. "The other thread":http://qt-project.org/forums/viewthread/26147/#119546 is all about that loop and getting rid of it. But I think that in my actual case this will likely not be a real issue since the datagrams are not frequent enough. (At least in developer's environment, not sure how about client's environment.)[/quote]
The stop() slot can be called from whereever you want, thats the benefit of sticking to signal slot connections when using QThread. I guess in your case the NetworkWatcher destructor is a good place, but you cannot call the slot directly. Either emit a signal that is connected to the stop slot or use the QMetaObject::invokeMethod() method.
The whole loop thing can be a problem in QThreads, because they can block the event loop. And if you rely on the event loop (as for example with your stop() slot) you should avoid busy loops. I usually try to solve the whole loop problem by using a sort of signal slot loop with boolean flags for stop, pause or whatever. This way you return to the event loop after every iteration of your "loop" and thus allow other signals from outside to be handled.
-
Along your suggestion I change the code to following:
@#include <QtCore/QByteArray>
#include <QtCore/QDebug>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtCore/QtGlobal>
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QNetworkInterface>
#include <QtNetwork/QUdpSocket>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>// Required to use QHostAddress as parameter in signals.
Q_DECLARE_METATYPE( QHostAddress )////////////////////////////////////////////////////////////////////////////////
class UDPListener
: public QObject
{
Q_OBJECTpublic:
UDPListener( QObject* parent = nullptr )
: QObject( parent ),
theSocketPtr( new QUdpSocket( this ) ),
theShouldStop( false ),
theFinishedEmitted( false )
{
// Required to use QHostAddress as parameter in signals.
qRegisterMetaType< QHostAddress >();if ( !theSocketPtr->bind( QHostAddress::AnyIPv4, 21212, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint ) ) { Q_ASSERT( false ); } Q_ASSERT( theSocketPtr->state() == QAbstractSocket::BoundState ); if ( !theSocketPtr->joinMulticastGroup( QHostAddress( "224.0.0.66" ) ) ) { Q_ASSERT( false ); }
}
virtual ~UDPListener()
{
qDebug() << "UDPListener::~UDPListener()";
}public slots:
void run()
{
connect(
theSocketPtr,
&QUdpSocket::readyRead,
this,
&UDPListener::processDatagrams
);processDatagrams();
}
void stop()
{
theShouldStop = true;
}private slots:
void processDatagram()
{
QByteArray datagram;
QHostAddress senderAddres;
quint16 senderPort;datagram.resize( theSocketPtr->pendingDatagramSize() ); theSocketPtr->readDatagram( datagram.data(), datagram.size(), &senderAddres, &senderPort ); emit datagramReceived( datagram, senderAddres, senderPort );
}
void processDatagrams()
{
if ( theFinishedEmitted )
return;while ( theSocketPtr->hasPendingDatagrams() && !theShouldStop ) processDatagram(); if ( theShouldStop ) { emit finished(); theFinishedEmitted = true; }
}
signals:
void datagramReceived(
const QByteArray& aDatagram,
const QHostAddress& aSenderAddress,
quint16 aSenderPort
);void finished();
private:
QUdpSocket* theSocketPtr;bool theShouldStop;
bool theFinishedEmitted;
};
////////////////////////////////////////////////////////////////////////////////
class NetworkWatcher
: public QObject
{
Q_OBJECTpublic:
NetworkWatcher( QObject* parent = nullptr )
: QObject( parent ),
theUDPListenerThreadPtr(),
theUDPListenerPtr()
{
}virtual ~NetworkWatcher()
{
qDebug() << "NetworkWatcher::~NetworkWatcher()";QMetaObject::invokeMethod( theUDPListenerPtr, "stop", Qt::QueuedConnection ); //theUDPListenerThreadPtr->quit();
}
void runOnNewThread()
{
theUDPListenerPtr = new UDPListener();
connect(
theUDPListenerPtr,
&UDPListener::datagramReceived,
this,
&NetworkWatcher::onDatagramReceived
);theUDPListenerThreadPtr = new QThread(); theUDPListenerThreadPtr->setObjectName( "NetworkWatcher::UDPListener" ); connect( theUDPListenerPtr, &UDPListener::finished, theUDPListenerThreadPtr, &QThread::quit ); connect( theUDPListenerThreadPtr, &QThread::started, theUDPListenerPtr, &UDPListener::run ); connect( theUDPListenerThreadPtr, &QThread::finished, theUDPListenerPtr, &UDPListener::deleteLater ); connect( theUDPListenerThreadPtr, &QThread::finished, theUDPListenerThreadPtr, &QThread::deleteLater ); theUDPListenerPtr->moveToThread( theUDPListenerThreadPtr ); theUDPListenerThreadPtr->start();
}
protected slots:
void onDatagramReceived(
const QByteArray& aDatagram,
const QHostAddress& aSenderAddress,
quint16 aSenderPort
)
{
/* ... */
}private:
QThread* theUDPListenerThreadPtr;UDPListener* theUDPListenerPtr;
};
////////////////////////////////////////////////////////////////////////////////
class Application
: public QApplication
{
Q_OBJECTpublic:
Application( int& argc, char** argv )
: QApplication( argc, argv ),
theNetworkWatcherPtr( new NetworkWatcher( this ) )
{
theNetworkWatcherPtr->runOnNewThread();
}virtual ~Application()
{
qDebug() << "Application::~Application()";
}private:
NetworkWatcher* theNetworkWatcherPtr;};
////////////////////////////////////////////////////////////////////////////////
int main( int argc, char* argv[] )
{
Application application( argc, argv );QMainWindow mainWindow;
mainWindow.show();return application.exec();
}@
But still UDPListener destructor is not called. -
Your code relies on this order of calling/signalling:
Application::~Application() calls NetworkWatcher::~NetworkWatcher() via parent-child relationship
NetworkWatcher::~NetworkWatcher() invokes UDPListener::stop() [queued]
UDPListener::stop() sets theShouldStop = true
UDPListener::processDatagrams() checks theShouldStop, emits UDPListener::finished()
UDPListener::finished() invokes QThread::quit() [queued]
QThread::quit() emits QThread::finished()
QThread::finished() invokes UDPListener::deleteLater() and QThread::deleteLater() [queued]
The problem is, you've already terminated the main event loop in step #1. Thus, step #5 cannot happen; theUDPListenerThreadPtr lives in the main thread, so its slots can no longer be invoked after Application::~Application() returns.
Solution: Do not use Application::~Application() to trigger cleanup. Instead, the application should only be allowed to quit AFTER all cleanup has been completed -- e.g. connect QThread::deleted() to QApplication::quit(), and then clean up everything when the main window is closed.
Also, there's no need to subclass QApplication -- just make the NetworkWatcher a local variable/object in main().