I've read many stackoverflow answers and a few blog posts(including QThread documentation as well) but I'm still not sure how to handle exiting main app while QThreads are still running(and not crash application meanwhile). What makes this difficult is the fact that those QThread instances contain different sorts of blocking functions such as curl queries, c++ file io, etc. Making it hard to use isInterruption requested API(which I would've used in an ideal case). To top that, I've to make sure these work fine across Linux, Windows, and OSX. We can assume here any file io corruption or incomplete network queries don't worry us and are taken care of. Here is the whole working example zipped. I use Qthreads in two different ways, which I'll demonstrate here, and the general idea is to bind application's aboutToQuit()
with thread's terminate()
assuming that this is safe since the app is going to close anyway -
Worker/Controller
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(const QString &msg) { QThread::sleep(10); emit resultReady("Finished"); } // simulates thread doing work
signals:
void resultReady(const QString &result);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(terminateThread()));
workerThread.start();
}
~Controller() { workerThread.quit(); workerThread.wait(); }
public slots:
void handleResults(const QString &msg);
void terminateThread() { workerThread.terminate(); workerThread.wait(); }
signals:
void operate(const QString &msg);
};
// can be started like
Controller c;
emit c.operate("Starting Thread");
Method run() override
class WorkerThread : public QThread
{
Q_OBJECT
void run() override { QThread::sleep(10); emit resultReady("Finished");}
signals:
void resultReady(const QString &s);
public slots:
void terminateThread() { workerThread.terminate(); workerThread.wait(); }
};
class MyObject : public QObject {
Q_OBJECT
public slots:
void handleResults(const QString &msg);
public:
void startWorkInAThread() {
WorkerThread *workerThread = new WorkerThread();
connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), workerThread, SLOT(terminateThread()));
workerThread->start();
}
};
// can be started like
MyObject obj;
obj.startWorkInAThread();
and I test both of these methods using main() that looks like -
QCoreApplication a(argc, argv);
std::puts("Starting app");
// Controller c;
// emit c.operate("Starting Thread");
MyObject obj;
obj.startWorkInAThread();
QTimer::singleShot(2000, [&]{
std::puts("Quit pressed");
a.quit();
});
return a.exec();
Now the issues -
If I use Controller method, Linux gives some error message about exception not being caught while exiting. That message goes away if I remove .wait() calls but those are supposedly necessary. Windows exits fine but the return code is some negative large number. OSX crashes the application, maybe due to .wait() call.
Qt has caught an exception thrown from an event handler. Throwing
exceptions from an event handler is not supported in Qt.
You must not let any exception whatsoever propagate through Qt code.
If that is not possible, in Qt 5 you must at least reimplement
QCoreApplication::notify() and catch all exceptions there.
(process:3416): GLib-CRITICAL **: 22:45:51.656: g_source_unref_internal: assertion 'source != NULL' failed
If I use run() overriding, Linux works perfectly fine and so does OSX. On Windows the program never finishes, stuck on .terminate() call or if I remove that then on .wait() call.
There are also some usages(in actual application, not in given examples) where the run() overriding is used but the run method's owner class is initialized as - WorkerThread *workerThread = new WorkerThread(this);
and due to this
being setup as it's parent, it crashes the program on exit somehow.
I actually don't want to wait on threads and want them to exit asap when my program quits, some blocking network operations could take several minutes to complete.
I suppose I've explained everything in detail but to summarize, I want my app to quit gracefully with running background threads and not crash at all. And do this on all supported(mentioned) QT platforms.
In your example project, just replace workerThread.terminate()
by workerThread.quit()
in the Controller::terminateThread()
method.
The usage of QThread::terminate()
is described as dangerous and discouraged in the Qt documentation. Using QThread::quit()
allow the worker thread event loop to gently handle this after his long work (sleep).
As you said, the best way to make long running tasks cleanly interruptible should be to use the QThread::requestInterruption
system and check during long operation if an interruption is requested.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With