Pergunta

In the example below (inside Qt GUI application) a new thread is started (with an event loop in which I want some work to be done):

void doWork()
{
   QThread* workerThread = new QThread();

   Worker* worker = new Worker();
   worker->moveToThread(workerThread);

   connect(workerThread, SIGNAL(started()), worker, SLOT(startWork()));
   connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));

   connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
   connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));

   workerThread->start();
}

startWork() can be a long running operation during which the application can be closed.

I expected that the application will not be closed as long as startWork() is being executed on the workerThread. It seems though, that when I close the last application window, the workerThread is gone instantaneously (during long running operation) and application closes without problems.

The questions arose:

  1. Why was the workerThread wiped right away?
    • Is it some parent/child issue?
    • How Qt handles such situation?
  2. Is it programmer mistake, not to call wait() on QThread (eventually)?
    • Even if so, I tried to wait() inside a slot for aboutToQuit() and application wasn't closed after long running operation was done (with setup as above). Only quit(); wait(); (inside the slot mentioned) allowed the application to close. Why?
Foi útil?

Solução

QThread has, basically, a long-standing API bug: it isn't always in a destructible state. In C++, an object is considered to be in destructible state when it's safe to invoke its destructor. Destructing a running QThread is an error. A QThread is merely a thread controller, it's not the "thread" itself. Think of how QFile acts: you can destruct it at any time, whether it's open or not. It truly encapsulates the notion of a file as a resource. A QThread is too thin of a wrapper around the native (system) thread: when you destruct it, it does not terminate nor dispose of the native thread if there is one. This is a resource leak (threads are OS resources), and people trip over this issue over and over again.

When the application's main() function returns, your implementation of the C/C++ runtime library happens to terminate all of the application's threads, effectively terminating the entirety of the application. Whether this is the behavior you desire is up to you. You're supposed to quit() and wait() your event-loop-running thread. For threads without an event loop, quit() is a no-op and you must implement your own quit flag. You must wait() on the thread before you destruct it. This is to prevent race conditions.

Below is a safe wrapper for QThread. It is a final class, since you can't reimplement run. This is important, since a reimplementation of run could be done in such a way that makes quit a no-op, breaking the contract of the class.

#include <QThread>
#include <QPointer>

class Thread : public QThread {
   using QThread::run; // final
public:
   Thread(QObject * parent = 0) : QThread(parent) {}
   ~Thread() { quit(); wait(); }
};

class ThreadQuitter {
public:
   typedef QList<QPointer<Thread>> List;
private:
   List m_threads;
   Q_DISABLE_COPY(ThreadQuitter)
public:
   ThreadQuitter() {}
   ThreadQuitter(const List & threads) : m_threads(threads) {}
   ThreadQuitter(List && threads) : m_threads(std::move(threads)) {}
   ThreadQuitter & operator<<(Thread* thread) { 
     m_threads << thread; return *this;
   }
   ThreadQuitter & operator<<(Thread& thread) {
     m_threads << &thread; return *this;
   }
   ~ThreadQuitter() {
      foreach(Thread* thread, m_threads) thread->quit();
   }
};

It could be used as follows:

#include <QCoreApplication>

int main(int argc, char ** argv) {
   QCoreApplication app(argc, argv);
   QObject worker1, worker2;
   Thread thread1, thread2;
   // Style 1
   ThreadQuitter quitter;
   quitter << thread1 << thread2;
   // Style 2
   ThreadQuitter quitterB(ThreadQuitter::List() << &thread1 << &thread2);
   //
   worker1.moveToThread(&thread1);
   worker2.moveToThread(&thread2);
   thread1.start();
   thread2.start();

   QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection);
   return app.exec();
}

Upon return from main, the thread quitter will quit() all worker threads. This allows the threads to wind down in parallel. Then, thread2.~Thread will wait for that thread to finish, then thread1.~Thread will do the same. The threads are now gone, the objects are threadless and can be safely destructed: worker2.~QObject is invoked first, followed by worker1.~QObject.

Outras dicas

1) Is it parent / child issue?

Not in the case of your code - You're not parenting the QThread. Qt doesn't like you to just terminate the main thread if you've other threads running. You're likely to see it complain in the standard output that the other thread was still running when the application was terminated. However, Qt will kill the other thread, which is why there's a function to call and wait for the thread to terminate properly.

2) Is it programmer mistake not to call wait()?

Yes. If you're having issues with the Thread not quitting properly, it's because you're not handling it correctly, in which case you could open another question and show the code as to how you're handling the wait before quitting.

when I close the last application window, the workerThread is gone instantaneously

Note that there's a function in QApplication called setQuitOnLastWindowClosed, which you can set to false, to prevent the app automatically quitting on closing the last window.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top