如果在堆上定义,QProcess 不会 kill/terminate 进程

QProcess doesn't kill/terminate the process if be defined on heap

我想kill/terminate我在应用程序退出时创建的进程:

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    QPushButton w; w.show();
    struct Lambda {
        static void run() {
            static QProcess p; //version 1
//            QProcess& p = *new QProcess(qApp); //version 2
            p.connect(qApp, &QApplication::aboutToQuit, &p, &QProcess::kill);
            p.connect(qApp, &QApplication::aboutToQuit, &p, &QProcess::terminate);
            p.connect(qApp, &QApplication::aboutToQuit, &p, &QProcess::close);
            p.connect(qApp, &QApplication::aboutToQuit, &p, &QProcess::deleteLater);
            p.start("caffeinate -d");
        }
    };
    QtConcurrent::run(Lambda::run);
    return a.exec();
}

版本 1:我的应用程序 运行 如我所料:成功创建和终止进程,但退出应用程序时,QCreator 报告:"QProcess: Destroyed while process ("caffeinate") 仍然是 运行宁."

对于版本 2:我的应用程序可以启动进程但不能 kill/terminate 退出进程,并且没有像上面那样的报告。

我只想问一下为什么QProcess在堆上创建的时候不能像stative版本那样被kill掉?谢谢!

(我使用 struct Lambda 因为我不能在我的项目中使用 c++11 lambda)

tl;博士

在这两种情况下,信号都没有传递;在第一种情况下,析构函数会终止进程,在第二种情况下,它甚至没有机会 运行.

总的来说,您的代码是对几乎所有 QObjectQThread、信号等禁忌事项的一个很好的概括; 阅读 Threads and QObjects before doing anything with threads, QObjects and signals in Qt. This is essential information without which you'll only do a mess like this.* Also this wiki 文章提供了很好的 运行在 Qt 中使用线程的“正确方法”。


详细解释

让我们调用主线程线程AQtConcurrent::run启动的线程线程B.

案例一

run是来自第二个线程的运行时,p被创建,所以它有thread affinity with thread B. For this reason, all the connect you perform on it are queued connectionsconnect的默认是AutoConnection,如果连接的对象具有不同的线程关联,则使用 QueuedConnection - 并且 qApp 线程 A).

中创建

问题是,排队的连接只有在接收线程有事件循环 运行ning 时才工作(它们被实现为 sendEvent,所以如果目标中没有事件循环处理事件thread 他们只堆积在事件队列中),而这里 run returns 刚开始进程。

因此,killterminateclosedeleteLater 永远不会被调用。请注意:

  • 在这种情况下调用 deleteLater 无论如何都会出错,因为它会尝试对 static 对象执行 delete
  • killterminate 都不是同步的,因此要确保进程在继续之前已经停止,您还需要 waitForFinished;
  • 另外,QtConcurrent::run 旋转的线程可能会在 run 终止后死掉1;这绝对是一件坏事,因为您将有 QObjects 与死线程的线程亲和性一起放置。我不知道sendEvent如何优雅地处理这种情况。

无论如何,当程序结束时,p 的析构函数将作为 C++ 应用程序关闭的正常部分自动调用2as documentedQProcess 的析构函数终止它链接到的进程,如果它仍然是 运行ning(但也会写出你看到的“可怕的消息”)。

案例二

与案例 1 一样,您正在创建具有 线程 B 关联的 QProcess;所以我们上面所说的所有关于未交付的事件 & co。仍然适用。

这里主要有以下三个区别:

  • 您正在将 p 的父级设置为 qApp,它位于主线程中;这是明确不允许的,QObject 之间的所有父子关系必须存在于具有相同线程关联的对象之间;可能您在控制台中收到了有关此事实的一些警告消息(setParent 明确检查对象是否存在于同一线程中,我希望 QObject 的构造函数执行相同的操作);
  • 在这种情况下,deleteLater 可能是合适的(如果你有一个事件循环旋转),因为你分配了 new;
  • 但最重要的是,这里 p 的析构函数从未被调用,因为它已分配给 new 并且没有人在其上调用 delete;由于这个原因,启动的进程保持 运行ning(另外,你有一个小的内存泄漏)。

那么,处理这个问题的正确方法是什么?就个人而言,我会完全避免使用线程和信号。启动进程已经是异步的,因此您可以简单地完成:

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    QProcess p;
    p.start("caffeinate -d");
    QPushButton w; w.show();
    int ret = a.exec();
    p.close();
    return ret;
}

与线程、事件队列、信号等一样:不要让它变得比需要的更复杂。


脚注

  1. 实际上在这种情况下您可能不会注意到,因为 QtConcurrent 使用全局线程池,它只会在空闲 30 秒后杀死旋转线程。

  2. 一般提示:您通常不希望以这种方式销毁“复杂”对象,因为 main 已经终止,所以 (1) 这会使调试变得更加复杂和 (2) 如果您有依赖 QApplication 的 Qt 对象仍然存在(通常是 QtGui 和 QtWidgets 中的所有内容),您将在程序终止时开始出现奇怪的崩溃。