我正在编写一个简单的控制台应用程序,它使用 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,应用程序会干净地退出并将控制返回到命令提示符。
最佳答案
这里有两个问题:
当使用 Ctrl-C 终止应用程序时,它会收到来自操作系统的 Kill 或 Abort 信号,并尽快终止。因此,不会因为正常堆栈展开的中断而触发析构函数或 aboutToQuit 信号。
您的 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/