Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
271 views
in Technique[技术] by (71.8m points)

c++ - What happens to QThread when application is being closed without proper wait() call?

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?
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...