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

标签 windows multithreading qt threadpool

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

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

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

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

定时器.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

主要.cpp

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


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

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

最佳答案

这里有两个问题:

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

  2. 您的 worker 类不处理中断。通常一个运行函数有某种循环来迭代一些数据 block 的工作。为了优雅地停止 QRunnable 你必须退出运行功能。您可以安全地使用 std::atomic bool 成员变量将循环打断到 MyWorker 类中并使用信号/插槽来切换它。

这里是问题 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

MyWorker类继承自QObject,利用信号/槽退出run函数处理循环。 bool 值“aborted”是一个原子变量,以保证线程安全地访问它。 (原子是 c++11 的一个特性)

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

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

关于windows - Qt 多头控制台应用程序在退出时挂起(使用 ctrl-c),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55463306/

相关文章:

windows - 如何以编程方式判断 Windows PE 文件是控制台子系统还是 Windows 子系统?

java - AsyncTask 和套接字在 android java 中不起作用

python - 我需要服务器向所有客户端发送消息(Python、套接字)

c++ - Qt 读取访问冲突位于 :0x0, flags=0x0(第一次机会)

c++ - QtQuick : QQmlApplicationEngine failed to load component qrc:/main. qml:23 无效的附加对象分配

windows - 'slc' 不是内部或外部命令,也不是可运行的程序或批处理文件

python - 如何将图标添加到 Python pip 生成的可执行文件的入口 pip

windows - 使用 Administrator 以外的帐户使用 "net use"命令

c# - 并行 Foreach 超时优雅地关闭每个工作线程

python - 如何在 QTextEdit 中滚动行号