Qt 多头控制台应用程序在退出时挂起(使用 ctrl-c)

Qt multi-thead console application hangs on exit (using ctrl-c)

我正在编写一个简单的控制台应用程序,它使用 QThreadPool 生成一些工作程序。我正在 Windows 10 上编译,使用 Qt 5.12.2 和 Microsoft Visual C++ Compiler 14.0 (amd64)。工作需要使用计时器定期开始,但工作时间比间隔时间长。 8个线程应该能够跟上工作负载。我启动了该应用程序,它运行得非常顺利并且符合预期。

当我想退出命令行应用程序时,我按 ctrl-c 停止。然后应用程序挂起。计时器停止并且所有输出停止,但它不会 return 我进入命令提示符。我必须打开任务管理器才能退出应用程序。我确定它与未正确清理 QThreadPool 有关,但我找不到如何清理它。感谢您的建议。

我尝试捕捉来自计时器的销毁信号和来自应用程序的 aboutToQuit 信号。一个都不火。如果您注释掉线程池的开始,应用程序将正确退出。我还在 MyTimer class 中创建了线程池作为成员变量,并将 worker 转换为指针。所有变化都导致相同的结果,退出时挂起。

我在这台机器上没有调试器来附加和查看应用程序挂起的位置。

mytimer.h

#ifndef MYTIMER_H
#define MYTIMER_H

#include <QDebug>
#include <QTimer>
#include <QThreadPool>

class MyWorker : public QRunnable
{
public:
    void run()
    {
        QThread::msleep(150);  //simulate some work
        qDebug() << ".";
    }
};

class MyTimer : public QObject
{
    Q_OBJECT
public:
    MyTimer()
    {
        QThreadPool::globalInstance()->setMaxThreadCount(8);
        worker.setAutoDelete(false);
        // setup signal and slot
        connect(&timer, SIGNAL(timeout()), this, SLOT(MyTimerSlot()));

        timer.setTimerType(Qt::PreciseTimer);
        // msec
        timer.start(50);
    }

    QTimer timer;
    MyWorker worker;

public slots:
    void MyTimerSlot()
    {
        //Comment the below line and the ctrl-c will work.
        QThreadPool::globalInstance()->start(&worker);
        qDebug() << "-";
    }
};


#endif // MYTIMER_H

main.cpp

#include <QCoreApplication>
#include "mytimer.h"


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MyTimer timer;
    return a.exec();
}

我希望当我在应用程序执行时按 ctrl-c,应用程序会干净地退出并return控制命令提示符。

你这里有两个问题:

  1. 当使用 Ctrl-C 终止应用程序时,它会收到来自 Os 的 Kill 或 Abort 信号,并尽快终止。因此,不会因为正常堆栈展开的中断而触发析构函数或 aboutToQuit 信号。

  2. 您的工人 class 不处理中断。通常一个 运行 函数有某种循环来迭代一些数据块的工作。为了优雅地停止 QRunnable,您必须退出 运行 函数。您可以安全地使用 std::atomic 布尔成员变量将循环中断到 MyWorker class 并使用 signal/slots 来切换它。

这是问题 1 的解决方法:

#include <QCoreApplication>
#include <csignal>
#include "mytimer.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyTimer timer;

    QObject::connect(qApp, &QCoreApplication::aboutToQuit, &timer, &MyTimer::workerStopRequested);

    signal(SIGTERM, [](int sig) { qApp->quit(); });
    signal(SIGABRT, [](int sig) { qApp->quit(); });
    signal(SIGINT, [](int sig) { qApp->quit(); });
    signal(SIGKILL, [](int sig){ qApp->quit(); });

    return a.exec();
}

使用 csignal 头文件中提供的实用程序,您可以捕获终止信号并强制应用程序退出,触发 aboutToQuit 信号。

此信号还用于告知 MyTimer 实例停止其工作人员触发 workerStopRequested 信号,这是问题 2 的解决方案的一部分:

#ifndef MYTIMER_H
#define MYTIMER_H

#include <QDebug>
#include <QTimer>
#include <QThreadPool>

class MyWorker : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit MyWorker(QObject* parent = nullptr) :
        QObject(parent),
        QRunnable()
    {
        aborted = false;
    }

    void run()
    {
        while (!aborted)
        {
            QThread::msleep(150);  //simulate some work
            qDebug() << ".";
        }
    }

public slots:
    void abort()
    {
        aborted = true;
        qDebug() << "stopped.";
    }

protected:
    std::atomic<bool> aborted;
};

class MyTimer : public QObject
{
    Q_OBJECT
public:
    MyTimer()
    {
        QThreadPool::globalInstance()->setMaxThreadCount(8);
        worker.setAutoDelete(false); // <-- Good. Worker is not a simple QRunnable anymore

        // setup signal and slot
        connect(&timer, SIGNAL(timeout()), this, SLOT(MyTimerSlot()));

        connect(this, &MyTimer::workerStopRequested, &worker, &MyWorker::abort);

        timer.setTimerType(Qt::PreciseTimer);
        // msec
        timer.start(50);

    }

    QTimer timer;
    MyWorker worker;

signals:
    void workerStopRequested();

public slots:
    void MyTimerSlot()
    {
        //Comment the below line and the ctrl-c will work.
        QThreadPool::globalInstance()->start(&worker);
        qDebug() << "-";
    }
};

#endif // MYTIMER_H

MyWorkerclass继承自QObject利用signal/slots退出运行函数处理循环。布尔值 "aborted" 是一个原子变量,以保证线程安全地访问它。 (原子是 c++11 的特性)

在发出 workerStopRequested 信号(参见 main.cpp)并执行 abort() 槽后,它被设置为 false,这是在 MyTimer class 的构造函数中建立连接的结果.

请注意,当切换中止时,它会导致处理循环在下一次迭代时停止。这意味着您可以在 "stopped" 字符串之后看到屏幕上最多打印 8 个点(根据最大线程数,每个线程一个)。