如何在应用程序退出()期间处理 Qthread 终止?

Posted

技术标签:

【中文标题】如何在应用程序退出()期间处理 Qthread 终止?【英文标题】:How to handle Qthread termination during app quit()? 【发布时间】:2021-01-15 08:53:26 【问题描述】:

我已经阅读了许多 *** 答案和一些博客文章(也包括 QThread 文档),但我仍然不确定如何在 QThreads 仍在运行时处理退出的主应用程序(同时不会导致应用程序崩溃)。使这变得困难的事实是,这些 QThread 实例包含不同类型的阻塞函数,例如 curl 查询、c++ 文件 io 等。这使得使用 isInterruption 请求的 API 变得很困难(我会在理想情况下使用它)。最重要的是,我必须确保这些在 Linux、Windows 和 OSX 上都能正常工作。我们可以在这里假设任何文件 io 损坏或不完整的网络查询都不会让我们担心并得到照顾。这是整个工作示例zipped。我以两种不同的方式使用 Qthreads,我将在这里进行演示,总体思路是将应用程序的 aboutToQuit() 与线程的 terminate() 绑定,假设这是安全的,因为无论如何应用程序都会关闭 -


    工人/控制器

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

    方法 run() 覆盖

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

我使用 main() 测试这两种方法,看起来像 -

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

现在的问题 -

    如果我使用 Controller 方法,Linux 会给出一些关于退出时未捕获异常的错误消息。如果我删除 .wait() 调用,该消息就会消失,但那些应该是必要的。 Windows 退出正常,但返回码是一些负数。 OSX 使应用程序崩溃,可能是由于 .wait() 调用。

     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
    

    如果我使用 run() 覆盖,Linux 工作得非常好,OSX 也是如此。在 Windows 上,程序永远不会完成,卡在 .terminate() 调用上,或者如果我删除它,然后在 .wait() 调用上。

    还有一些用法(在实际应用中,不在给定示例中)使用 run() 覆盖但 run 方法的所有者类被初始化为 - WorkerThread *workerThread = new WorkerThread(this); 并且由于 this 被设置为它的父类,它以某种方式在退出时使程序崩溃。

    我实际上不想等待线程并希望它们在我的程序退出时尽快退出,一些阻塞网络操作可能需要几分钟才能完成。


我想我已经详细解释了所有内容,但总而言之,我希望我的应用程序在运行后台线程的情况下优雅地退出,并且根本不会崩溃。并在所有受支持(提及)的 QT 平台上执行此操作。

【问题讨论】:

如果你想要一些通用的方法来停止线程&不等待&不崩溃&不做任何正确的停止阻塞操作&同时在 - 这是不可能的 【参考方案1】:

在您的示例项目中,只需在 Controller::terminateThread() 方法中将 workerThread.terminate() 替换为 workerThread.quit()

QThread::terminate() 的用法在 Qt 文档中描述为 dangerous and discouraged。使用QThread::quit() 允许工作线程事件循环在他长时间工作(睡眠)后温和地处理这个问题。

正如您所说,让长时间运行的任务完全可中断的最佳方法应该是使用QThread::requestInterruption 系统并在长时间运行期间检查是否请求中断。

【讨论】:

我不能使用QThread::requestInterruption和朋友的原因是我的代码包含阻塞调用。当这些阻塞调用正在运行并且足够长时(比如 curl 上传调用需要 2 分钟上传文件),我无法检查中断,然后退出导致崩溃(如果使用终止)或程序直到 2 分钟过去才退出,上传终于完成。所以用 quit() 替换 terminate() 对我的情况并没有真正的帮助,因为 quit() 使程序不会退出。 如果您的代码使用阻塞调用,则无法按照您希望的方式彻底退出线程。每个异步服务都应该有一个长任务的进度通知(例如 curl 上传的进度百分比)和取消它的方法。在每次通知进度时,服务可以检查QThread::currentThread()->isInterruptionRequested(),如果需要,在退出前自行清理。 你说得对,curl 确实有一个异步接口,但我仍然无法使用它,因为它已经广泛用于同步 api。然后有文件 io 操作再次同步。同步的库很少。我希望每个都有异步 api,但这就是我发布这个问题的原因。

以上是关于如何在应用程序退出()期间处理 Qthread 终止?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用pytest正确退出队列和Qthread进行测试?

在计算期间在 PySide(或 PyQt)中使用 QProgressBar 实现 QThread

如何从 GUI 停止 QThread

RPA应用场景-日终清算操作

Qt:qthread在关闭期间线程仍在运行时被破坏

线程应当如何正常退出?