Qt Forum

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Unsolved

    Call for Presentations - Qt World Summit

    Stopping and deleting background QThread and its worker

    General and Desktop
    4
    16
    16112
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • A
      Adam Badura last edited by

      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)?

      1 Reply Last reply Reply Quote 0
      • M
        mcosta last edited by

        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);
        @

        Once your problem is solved don't forget to:

        • Mark the thread as SOLVED using the Topic Tool menu
        • Vote up the answer(s) that helped you to solve the issue

        You can embed images using (http://imgur.com/) or (http://postimage.org/)

        1 Reply Last reply Reply Quote 0
        • A
          Adam Badura last edited by

          [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.

          1 Reply Last reply Reply Quote 0
          • M
            mcosta last edited by

            The problem is then to stop your worker.
            You could implement a slot 'stopWorking' in your Worker class; in your case you could close the socket and exit.

            If you post your Worker code, we can also be more specific

            Once your problem is solved don't forget to:

            • Mark the thread as SOLVED using the Topic Tool menu
            • Vote up the answer(s) that helped you to solve the issue

            You can embed images using (http://imgur.com/) or (http://postimage.org/)

            1 Reply Last reply Reply Quote 0
            • JKSH
              JKSH Moderators last edited by

              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.

              Qt Doc Search for browsers: forum.qt.io/topic/35616/web-browser-extension-for-improved-doc-searches

              1 Reply Last reply Reply Quote 0
              • A
                Adam Badura last edited by

                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).

                1 Reply Last reply Reply Quote 0
                • M
                  mcosta last edited by

                  Hi Adam,

                  if you want stop your UDPListener, you have to write it in such manner.
                  Please post your code.

                  Once your problem is solved don't forget to:

                  • Mark the thread as SOLVED using the Topic Tool menu
                  • Vote up the answer(s) that helped you to solve the issue

                  You can embed images using (http://imgur.com/) or (http://postimage.org/)

                  1 Reply Last reply Reply Quote 0
                  • A
                    Adam Badura last edited by

                    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_OBJECT

                    public:
                    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_OBJECT

                    public:
                    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_OBJECT

                    public:
                    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).

                    1 Reply Last reply Reply Quote 0
                    • M
                      mcosta last edited by

                      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"

                      Once your problem is solved don't forget to:

                      • Mark the thread as SOLVED using the Topic Tool menu
                      • Vote up the answer(s) that helped you to solve the issue

                      You can embed images using (http://imgur.com/) or (http://postimage.org/)

                      1 Reply Last reply Reply Quote 0
                      • K
                        KA51O last edited by

                        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?

                        1 Reply Last reply Reply Quote 0
                        • A
                          Adam Badura last edited by

                          [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.

                          1 Reply Last reply Reply Quote 0
                          • M
                            mcosta last edited by

                            Hi,

                            you MUST call QThread::quit in your code and conenct finished() SIGNAL to stop()

                            Once your problem is solved don't forget to:

                            • Mark the thread as SOLVED using the Topic Tool menu
                            • Vote up the answer(s) that helped you to solve the issue

                            You can embed images using (http://imgur.com/) or (http://postimage.org/)

                            1 Reply Last reply Reply Quote 0
                            • A
                              Adam Badura last edited by

                              [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.)

                              1 Reply Last reply Reply Quote 0
                              • K
                                KA51O last edited by

                                [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.

                                1 Reply Last reply Reply Quote 0
                                • A
                                  Adam Badura last edited by

                                  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_OBJECT

                                  public:
                                  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_OBJECT

                                  public:
                                  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_OBJECT

                                  public:
                                  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.

                                  1 Reply Last reply Reply Quote 0
                                  • JKSH
                                    JKSH Moderators last edited by

                                    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().

                                    Qt Doc Search for browsers: forum.qt.io/topic/35616/web-browser-extension-for-improved-doc-searches

                                    1 Reply Last reply Reply Quote 0
                                    • First post
                                      Last post