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

时间:2019-04-01 20:44:41

标签: windows multithreading qt threadpool

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

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

我尝试从计时器捕获破坏信号,并从应用程序捕获aboutToQuit信号。都没有人开火。如果您注释掉线程池的开始,则应用程序将正确退出。我还在MyTimer类中将线程池创建为成员变量,并将工作线程转换为指针。所有变化都会导致相同的结果,请在退出时挂起。

我在这台计算机上没有调试器可以连接并查看应用程序挂起的位置。

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时,应用程序将干净退出,并将控制权返回给命令提示符。

1 个答案:

答案 0 :(得分:0)

您在这里有两个问题:

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

  2. 您的工作者类不处理中断。通常,运行函数具有某种循环,可对某些数据块进行迭代。为了正常停止QRunnable,您必须退出运行功能。您可以使用std :: atomic布尔成员变量将循环分成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继承来利用信号/插槽从运行函数处理循环中退出。布尔值“ aborted”是一个原子变量,以确保可以线程安全地对其进行访问。 (原子是c ++ 11的功能)

由于在MyTimer类的构造函数中建立了连接,因此在发出workerStopRequested信号(请参阅main.cpp)并执行abort()插槽之后,将其设置为false。

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