ManuelSchneid3r ManuelSchneid3r - 3 months ago 103
C++ Question

QProcess not emitting its signals when not waitForFinished()

In the following code omitting the

waitForFinished()
makes the QProcess stop emitting its signal. What the heck is wrong with it? Is this a Qt Bug? (5.7). Note this code is run parallel with QtConcurrent run. But this should not change anything, should it? Afaik sending signals in other threads is fine, though they will be queued.

QProcess *process = new QProcess;
process->setReadChannel(QProcess::StandardOutput);

connect(process, &QProcess::readyReadStandardOutput, [](){
qDebug()<< "readyReadStandardOutput";
});

connect(process, &QProcess::stateChanged, [](QProcess::ProcessState state){
qDebug()<< "stateChanged"<< state;
});

connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
[=](){
qDebug()<< "finsished";
});

connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
[this, process](int exitCode, QProcess::ExitStatus exitStatus){
qDebug()<< "finsished";
if (exitStatus == QProcess::NormalExit && exitCode == 0){
while (process->canReadLine()) {
QString line = QString::fromLocal8Bit(process->readLine());
QRegularExpression regex("\"(.*)\" {(.*)}");
QRegularExpressionMatch match = regex.match(line);
names_.push_back(match.captured(1));
uuids_.push_back(match.captured(2));
}
}
process->deleteLater();
});
process->start("VBoxManage", {"list", "vms"});
process->waitForFinished(); // This line changes everything
qDebug()<< "leftWaitForFinished";

Answer

You're not running an event loop in the thread where the QProcess instance lives. Any QObject in a thread without an event loop is only partially functional - timers won't run, queued calls won't be delivered, etc. So you can't do that. Using QObjects with QtConcurrent::run requires care.

At the very least, you should have a temporary event loop for as long as the process lives - in that case you should hold QProcess by value, since deleteLater won't be executed after the event loop has quit.

QProcess process;
...
QEventLoop loop;
connect(process, &QProcess::finished, &loop, &QEventLoop::quit);
loop.exec();

Otherwise, you need to keep the process in a more durable thread, and keep that thread handle (QThread is but a handle!) in a thread that has an event loop that can dispose of it when it's done.

// This can be run from a lambda that runs in an arbitrary thread

auto thread = new QThread;
auto process = new QProcess;

...

connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
    [this, process](int exitCode, QProcess::ExitStatus exitStatus){
    ...
    process->deleteLater();
    process->thread()->quit();
});

process->start("VBoxManage",  {"list", "vms"});
process->moveToThread(thread);

// Move the thread **handle** to the main thread
thread->moveToThread(qApp->thread());
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
thread->start();

Alas, this is very silly since you're creating temporary threads and that's expensive and wasteful. You should have one additional worker thread where you take care of all low-overhead work such as QProcess interaction. That thread should always be running, and you can move all QProcess and similar object instances to it, from concurrent lambdas etc.