QProcess::readyReadStandardOutput signal real time emitting
это мой первый пост на данном ресурсе )
Прошу помощи разобраться с непонятным поведением разрабатываемой программы на C++ и Qt5.11.2 под Windows, компилируемой MSVC17. Основная программа (родительский процесс) выполняет запуск стороннего приложения (процесс потомка) (.exe) с помощью использования класса QProcess. При этом в родительском процессе должен выполняться вывод на печать сообщений, которые были выведены в процессе потомка в stdout в режиме реального времени. Код основной программы и стороннего приложения при ведены ниже.
Суть проблемы: печать в родительском процессе производится только один раз, хотя печать в stdout в процессе потомка производится дважды. При этом производится печать всего содержимого stdout процесса потомка и непосредственно перед изменением статуса процесса потомка с QProcess::Running на QProcess::NotRunning.
Предполагаю, что сигнал QProcess::readyReadStandardOutput() вызывается только однажды. В документации читал, что сигналы и слоты обрабатываются асинхронно, при этом сигнал QProcess::stateChanged() вызывается несколько раз, как и положено.
Пробовал, но не помогло (или плохо пробовал):
- Запуск процесс на вызов приложения с помощью вызова C:\Windows\System32\cmd.exe с параметрами "/C C:\path-to-app\child_application.exe"
- Добавление в конструктор AppProcess строки _application.setReadChannelMode(QProcess::Unbuffered);
- Добавление классу AppProcess свойства типа QThread, поручение этому дочернему потоку основного процесса обработку сигнала void AppProcess::log(const QString&)
Код программы, которая вызвана в родительском процессе. main.cp
...
AppProcess application = AppProcess(cwd, applicationName, parameters);
QObject::connect(&application, &AppProcess::log, [&cp](const QString& message) {
std::cout << "Report: " << message.toStdString();
});
application.startApp();
...
AppProcess.h
class AppProcess :
public QObject
{
Q_OBJECT
public:
AppProcess(const sfs::path& wd, const sfs::path& application,
const QString& parameters, QObject *parent = nullptr);
void startApp();
void stopApp();
signals:
void log(const QString& message);
private slots:
void errorOccured(const QProcess::ProcessError& error);
void readyReadStandardOutput();
void stateChanged(const QProcess::ProcessState& newState);
void finished(const int& exitCode, const QProcess::ExitStatus& exitStatus);
private:
bool _listening;
QProcess _application;
};
AppProcess.cpp
AppProcess::AppProcess(const sfs::path& wd, const sfs::path& application,
const QString& parameters, QObject *parent) :
_application(QProcess(this)),
_listening(false)
{
_application.setWorkingDirectory(QString(wd.string().c_str()));
_application.setProgram(QString(application.string().c_str()));
_application.setArguments(QStringList() << parameters);
_application.setReadChannelMode(QProcess::SeparateChannels);
_application.setCurrentReadChannel(QProcess::StandardOutput);
connect(&_application, &QProcess::errorOccurred,
this, &AppProcess::errorOccured);
connect(&_application, &QProcess::readyReadStandardOutput,
this, &AppProcess::readyReadStandardOutput);
connect(&_application, &QProcess::stateChanged,
this, &AppProcess::stateChanged);
connect(&_application, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, &AppProcess::finished);
}
void AppProcess::startApp()
{
_listening = true;
_application.start();
_application.waitForFinished(-1);
}
void AppProcess::stopApp()
{
_listening = false;
emit log("Closed by user!");
_application.kill();
}
void AppProcess::errorOccured(const QProcess::ProcessError& error)
{
if (!_listening) return;
switch (error)
{
case QProcess::FailedToStart:
emit log("Failed to start");
break;
case QProcess::Crashed:
emit log("Crashed");
break;
case QProcess::Timedout:
emit log("TimedOut");
break;
case QProcess::ReadError:
emit log("Read error");
break;
case QProcess::WriteError:
emit log("Write error");
break;
default:
emit log("Unknnown error");
break;
}
}
void AppProcess::readyReadStandardOutput()
{
if (!_listening) return;
emit log(_application.readAllStandardOutput().trimmed());
}
void AppProcess::stateChanged(const QProcess::ProcessState& newState)
{
if (!_listening) return;
switch (newState)
{
case QProcess::NotRunning:
emit log("Not Running");
break;
case QProcess::Starting:
emit log("Starting...");
break;
case QProcess::Running:
emit log("Running");
break;
}
}
void AppProcess::finished(const int& exitCode, const QProcess::ExitStatus& exitStatus)
{
if (!_listening) return;
emit log("ExitCode: " + QString::fromStdString(std::to_string(exitCode)) +
" ExitStatus: " + QString(exitStatus ? "CrashExit" : "NormalExit"));
}
Код программы, вызываемой в процессе потомка. child_application.cpp
int main()
{
std::cout << "just a simple message\r\n";
Sleep(10000);
std::cout << "just a simple message\r\n";
}
Вывод сообщений в stdout родительского процесса, полученных от процесса потомка.
Report: Starting...
Report: Running
Report: just a simple message
just a simple message
Report: Not Running
Report: ExitCode: 0 ExitStatus: NormalExit
Ответы (2 шт):
Поток std::cout при выводе буферизуется. Это значит, что данные, помещенные в поток, не сразу попадут в вызывающий процесс (или файл, или устройство вывода). А попадут они туда тогда, когда поток решит сбросить буфер. В вашем случае это происходит при завершении работы процесса.
Есть способ вручную сбросить буфер. Для этого можно вызвать функцию
std::cout.flush();
Или записать в поток символ конца строки:
std::cout << "just a simple message" << std::endl;
@maestro, спасибо большое вам за подсказку. Однако, я хочу немного его дополнить, основываясь на произведенных тестах.
Действительно использование std::endl для обозначения новой строки или ручной сброс буфера потока вывода на печать с помощью std::cout.flush() решает проблему. Однако, достичь желаемого результата мне удалось, если после каждого сброса буфера останавливать выполнения процесса на некоторый интервал.
Привожу некоторые примеры тестов. Код программы, вызываемой в процессе потомка. child_application.cpp
int main()
{
unsigned long waitMS = 100; //10; //0;
std::cout << "just a simple message1" << std::endl;
Sleep(waitMS);
std::cout << "just a simple message2" << std::endl;
Sleep(waitMS);
std::cout << "just a simple message3" << std::endl;
Sleep(waitMS);
std::cout << "just a simple message4" << std::endl;
Sleep(waitMS);
std::cout << "just a simple message5" << std::endl;
Sleep(waitMS);
std::cout << "just a simple message6" << std::endl;
Sleep(waitMS);
std::cout << "last message" << std::endl;
}
Вывод сообщений в stdout родительского процесса, полученных от процесса потомка, если waitMS=0.
Report: Starting...
Report: Running
Report: just a simple message1
just a simple message2
just a simple message3
just a simple message4
just a simple message5
just a simple message6
last message
Report: Not Running
Report: ExitCode: 0 ExitStatus: NormalExit
Вывод сообщений в stdout родительского процесса, полученных от процесса потомка, если waitMS=10.
Report: Starting...
Report: Running
Report: just a simple message1
Report: just a simple message2
Report: just a simple message3
just a simple message4
Report: just a simple message5
just a simple message6
Report: last message
Report: Not Running
Report: ExitCode: 0 ExitStatus: NormalExit
Вывод сообщений в stdout родительского процесса, полученных от процесса потомка, если waitMS=100.
Report: Starting...
Report: Running
Report: just a simple message1
Report: just a simple message2
Report: just a simple message3
Report: just a simple message4
Report: just a simple message5
Report: just a simple message6
Report: last message
Report: Not Running
Report: ExitCode: 0 ExitStatus: NormalExit