[SOLVED] QProcess, killing a running process from a Dialog freezes the GUI
-
Hello,
I searched through the web and I tried in any way but I couldn't find the solution to my problem.
I have a MainWindow, which has a button to show a dialog which in turn starts a QProcess. In the dialog I have a button to kill the process and to return to the main GUI.
The process is killed, but the main window freezes (the OS says that the application is not responding).
I created a small example that shows this problem (the main.cpp is omitted)
Dialog.h (a dialog with a push button to start the exe and the OK and Cancel standard buttons)
@
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QProcess>namespace Ui {
class Dialog;
}class Dialog : public QDialog
{
Q_OBJECTpublic:
explicit Dialog(QWidget *parent = 0);
~Dialog();
public slots:
void processFinished(int exitCode, QProcess::ExitStatus exitSt);
void processError(QProcess::ProcessError error);
void processOutput();
void cancelPressed();
private:
Ui::Dialog ui;
QProcess process;
};#endif // DIALOG_H
@Dialog.cpp, the implementation of the dialog, own the QProcess which starts the EXE with an argument (I replaced with dummy values)
@
#include "dialog.h"
#include "ui_dialog.h"
#include <QDebug>
#include <QPushButton>
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
process = new QProcess(this);
connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processFinished(int,QProcess::ExitStatus)));
connect(process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError)));
connect(process, &QProcess::readyRead, this, &Dialog::processOutput );connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &Dialog::cancelPressed); connect(ui->pushButton, &QPushButton::clicked, [this]() { process->start("MY EXE WITH VERY LONG COMPUTATION TIME", QStringList() << "AN ARGUMENT" );
});
}Dialog::~Dialog()
{
delete ui;
}void Dialog::processFinished(int exitCode, QProcess::ExitStatus exitSt)
{
qDebug() << "Status " << exitSt << " exitcode " << exitCode;
}void Dialog::processError(QProcess::ProcessError error)
{
qDebug() << "Error " << error;
}void Dialog::processOutput()
{
char buf[1024];
qint64 lineLength = process->readLine(buf, sizeof(buf));
if (lineLength != -1) {
qDebug() << QString(buf);
}
}void Dialog::cancelPressed()
{
process->kill();
accept();
}
@mainwindow.cpp has a pushbutton to exec() the dialog
@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "dialog.h"
#include <QtDebug>
MainWindow::MainWindow(QWidget parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
Dialog dialog = new Dialog(this);
connect(ui->pushButton, &QPushButton::clicked, dialog
{
dialog->exec();
qDebug() << "AAAAAA";
});
}MainWindow::~MainWindow()
{
delete ui;
}
@The "AAAAAA" string is shown on the console, but the GUI is completely freezed.
What can I do to kill a QProcess without freeze the GUI ?
Thank you,
Mario -
Did you try:
terminate instead of kill??
And to disconnect all signals from the QProcess before stopping the process.
That might help?
Isn't it better to start your external process in a different thread instead of in the dialog?
I do not believe the QProcess starts a different thread out of its own. So, it will consume lots of GUI resources from the MAinThread? -
Thank you for the fast response Jeroen,
Yes I tried also
@
disconnect(process, 0,0,0);
process->terminate();
@
and in the simple example I posted it works, but in my original program it doesn't, I have to figure out why.Yes, It is certainly better to start the external process in a different thread, however I'm puzzled why kill() (and terminate() ) hangs the GUI main thread after the Dialog has returned (and it prints the "AAAAA" strings after the dialog->exec() )
I forgot to say that my environment is Windows 8 64bit and I'm using QT 5.1 mingw.
I also noticed in the task manager a lot of process exe started by my application, it seems that the QProcess spawns a process that is not killed when the main application is closed.
I'll investigate more on that question and if I find a solution I'll post one.Thank you again,
Mario -
I investigated a bit by using ProcessExplorer.
The problem is that I’m invoking a process which in turns spawns multiple processes (it’s a build tool, visual builder pro).
On Windows process->terminate() doesn’t work, so I have to use process->kill(), but while it correctly terminates the “parent” process (visual builder pro command line tool), the child processes aren’t, leaving open handles (from TerminateProcess API page “http://msdn.microsoft.com/en-us/library/windows/desktop/ms686714(v=vs.85).aspx”: “When a process terminates, its kernel object is not destroyed until all processes that have open handles to the process have released those handles”).
This probably cause a problem in the GUI thread (which is the thread from which the QProcess->start() is called), which remains appended waiting for the handles to close.
I solved with an ugly hack, which also opens another cmd.exe window, but it works
@
#if defined(Q_OS_WIN)
Q_PID pid = process->pid();
QString cmdString = QString("taskkill /f /t /pid %1").arg(pid->dwProcessId);
qDebug() << "Executing " << cmdString;
system(cmdString.toStdString().c_str());
#endif
@
Mario -
Hi,
Isn't it a idea to have the external program run unattached? When that program is unable to be shutdown nicely, it's better to be left alone until it finishes on it's own. When the process is started detached it will not hang the GUI because there is no process to kill.
That might be prettier then the start of an extra external command box. -
Yes, it thought about that, but I need to capture the output from the process to show on my program, and QProcess provides a nice abstraction with signal/slot (on readRead .
The detached API for QProcess are all static and I can't attach to the output of the process.
Thank you again.Mario
-
An other option:
In your cancel button pressed start an 'extra' timer that calls the isFinished on the qprocess. Then in the TimerEvent or in the slot connected to the timer call accept() when when isFinished returns true.
The dialog should then set the cancel/ok button to gray and show a warning that the program is stopping the external program. Maybe include an total timer to it. -
even this option has a flaw, because I have to kill the process, since it may last for 10 minutes or more (the process it's a build tool, it compiles source code, copy files and so on).
Giving an hint that something is happening (with the timer and by disabling the cancel/ok buttons) is certainly better than freezing the GUI, but in my case it is not feasible to wait for so long.
If for example the user kills my program, I fear that I can incour in the problem of child zombies.
In fact it happened when I tried other solutions, with ProcessExplorer I saw a lot of orhpaned process, even if my program was stopped.
For the moment the hack I used is the solution that leaves the system in acceptable state (it kill the builder and all of its childs).
Naturally I can try to learn and use the Win32 API and do what taskkill does, but for my purpose it's not worth the time.
Thank you,Mario
-
I had a similar problem. I allocated myProcess on the stack rather than the heap. This is my code (it works). Without the disconnect statement my application was crashing on exit.
void GucViewModel::killProcess()
{
disconnect(&myProcess, 0, 0, 0);
myProcess.kill();
}