Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle Qthread termination during app quit()?

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 -


  1. 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");
    

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

  1. 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
    
  2. 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.

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

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

like image 275
Abhinav Gauniyal Avatar asked Sep 29 '20 17:09

Abhinav Gauniyal


1 Answers

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.

like image 91
Alaenix Avatar answered Nov 30 '22 04:11

Alaenix