In Qt, QProcess sits at the center of process management. It provides a complete abstraction for a child process’s lifecycle, standard I/O, and abnormal states. Around those capabilities, Qt defines six key signals that describe what happens from process startup to termination.
A useful way to read these signals is not as isolated callbacks, but as parts of one event flow. They map to four different aspects of process execution: state transitions, data streams, completion, and errors. Looking at them in that larger context makes them much easier to understand and use correctly.
A high-level view of the QProcess signal model
QProcess exposes the following signals:
1 2 3 4 5 6</th>
<th>void errorOccurred(QProcess::ProcessError error) void finished(int exitCode, QProcess::ExitStatus exitStatus = NormalExit) void readyReadStandardError() void readyReadStandardOutput() void started() void stateChanged(QProcess::ProcessState newState)</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
In a typical run, the signal sequence usually looks like this:
<table> <thead> <tr> <th>1 2 3 4 5 6 7</th>
<th>start() → stateChanged(Starting) → started() → stateChanged(Running) → readyReadStandardOutput() / readyReadStandardError() → finished() → stateChanged(NotRunning)</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
If something goes wrong, this may be interleaved with:
<table> <thead> <tr> <th>1</th>
<th>errorOccurred()</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
That sequence already reveals the structure behind the API. Some signals report state, some deliver I/O, some tell you the final result, and one exists specifically for process-level failures.
The important point is that these signals form a lifecycle. Memorizing them one by one is less useful than understanding where each one belongs in the lifetime of a process.
Lifecycle signals
Lifecycle-related signals describe how the process moves from one state to another. They are the foundation for everything else.
started(): the process was created successfully
After calling start(), this signal is emitted if the operating system successfully launches the child process.
1 2 3</th>
<th>connect(process, &QProcess::started, []() { // 进程已成功启动 });</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
This signal only tells you that startup succeeded. It does not mean the program has finished running, and it does not imply that any output is already available.
So started() is best understood as a clear starting marker. It confirms that the process has entered execution successfully and that later state changes or output handling can proceed on a valid basis.
stateChanged(): the core signal for state tracking
This signal is emitted whenever the process state changes. The possible states are those in QProcess::ProcessState:
1</th>
<th>NotRunning → Starting → Running → NotRunning</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
Example:
<table> <thead> <tr> <th>1 2 3 4</th>
<th>connect(process, &QProcess::stateChanged, [](QProcess::ProcessState newState) { // 可用于调试或状态跟踪 });</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
In practice, this signal is most useful when you need to observe or control state transitions closely. It is especially valuable during debugging, when you want to track how the process moves through its states, or in more complex logic where decisions depend on the exact execution phase.
Because it reflects the underlying state machine directly, stateChanged() is often the best entry point for understanding and monitoring the full lifecycle of a process.
finished(): the final lifecycle signal
This signal is emitted when the process exits, whether it ends successfully or not.
<table> <thead> <tr> <th>1 2 3 4</th>
<th>connect(process, &QProcess::finished, [](int exitCode, QProcess::ExitStatus exitStatus) { // 根据退出码判断执行结果 });</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
Parameters:
exitCode: the program’s return valueexitStatus:NormalExitCrashExit
A common check looks like this:
<table> <thead> <tr> <th>1 2 3</th>
<th>if (exitStatus == QProcess::NormalExit && exitCode == 0) { // 执行成功 }</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
This is the point where execution can be consolidated into a single result. By checking both the exit code and exit status, you can make a reliable judgment about how the child process ended and drive follow-up logic from that result.
Within the full lifecycle, finished() is the natural closing signal.
Output stream signals
In day-to-day development, these are usually the most frequently used signals.
Standard output: readyReadStandardOutput()
This signal is emitted when the child process writes data to stdout.
<table> <thead> <tr> <th>1 2 3</th>
<th>connect(process, &QProcess::readyReadStandardOutput, [process]() { QByteArray outputData = process->readAllStandardOutput(); // 读取标准输出 });</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
A key characteristic is that the signal is non-blocking. It does not interfere with the main event loop. Also, process output is usually produced in chunks, so this signal may be emitted multiple times, with each read returning only the data currently available in the buffer.
That makes it well suited for scenarios such as real-time log display or wrapping command-line tools. Instead of waiting for the process to end, you can continuously receive and handle output while it is still running.
Standard error: readyReadStandardError()
This signal is emitted when the child process writes data to stderr.
<table> <thead> <tr> <th>1 2 3</th>
<th>connect(process, &QProcess::readyReadStandardError, [process]() { QByteArray errorData = process->readAllStandardError(); // 读取错误输出 });</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
One important caveat: stderr does not automatically mean failure. Some programs write debug information or runtime logs to stderr, so it should not be treated as a direct indicator of an unsuccessful run. Its meaning depends on the behavior of the specific program and your own business logic.
The main value of both output-related signals is real-time visibility. They let your application keep consuming data while the process is alive, which is essential for interactive UIs, live logging, and systems that need good observability during command execution.
Error handling
errorOccurred(): process-level exceptions
This signal is emitted when an external or system-level error affects the process, for example:
<table> <thead> <tr> <th>1 2 3 4 5 6 7 8 9 10 11 12</th>
<th>connect(process, &QProcess::errorOccurred, [](QProcess::ProcessError error) { // 处理错误 /* 常见的错误类型(QProcess::ProcessError) FailedToStart // 程序不存在或无权限 Crashed // 运行崩溃 Timedout // 等待超时 ReadError WriteError */ });</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
The crucial distinction between this signal and readyReadStandardError() is that errorOccurred() reports process-level failures, not logical errors reported by the program itself.
If a program simply returns a non-zero status such as return 1, that does not trigger errorOccurred(). But if the executable path is wrong, permissions are insufficient, the process crashes, or an I/O problem prevents proper operation, then this signal can be emitted.
In real use, errorOccurred() often complements finished(). It gives you an earlier chance to detect and respond to system-level problems that are different in nature from ordinary business-level failure codes.
Quick reference
The table below can serve as a compact reference during development.
<table> <thead> <tr> <th>Signal</th> <th>Purpose</th> <th>When it is emitted</th> <th>What you can get</th> <th>Key distinction</th> </tr> </thead> <tbody> <tr> <td>started()</td>
<td>Marks successful startup</td>
<td>After start() successfully creates the child process</td>
<td>None</td>
<td>Only means startup succeeded; says nothing about output or completion</td>
</tr>
<tr>
<td>stateChanged()</td>
<td>Reports process state changes</td>
<td>When switching among NotRunning, Starting, and Running</td>
<td>Current state</td>
<td>The lowest-level state signal; lets you observe the full lifecycle</td>
</tr>
<tr>
<td>readyReadStandardOutput()</td>
<td>Receives standard output</td>
<td>When the child process writes to stdout (possibly multiple times)</td>
<td>stdout data</td>
<td>For normal output; data may arrive in chunks</td>
</tr>
<tr>
<td>readyReadStandardError()</td>
<td>Receives standard error output</td>
<td>When the child process writes to stderr (possibly multiple times)</td>
<td>stderr data</td>
<td>Does not equal failure; only indicates output on the error stream</td>
</tr>
<tr>
<td>finished()</td>
<td>Notifies process termination</td>
<td>When the process exits, successfully or not</td>
<td>exitCode, exitStatus</td>
<td>The lifecycle endpoint; used to consolidate execution results</td>
</tr>
<tr>
<td>errorOccurred()</td>
<td>Captures process-level exceptions</td>
<td>On startup failure, crash, I/O errors, and similar conditions</td>
<td>Error type</td>
<td>Only for system-level/process-level errors, not program logic errors</td>
</tr>
</tbody>
</table>
Taken together, these six signals describe nearly the entire execution story of a child process. started() and stateChanged() tell you where the process is, readyReadStandardOutput() and readyReadStandardError() tell you what it is producing, finished() tells you how it ended, and errorOccurred() tells you when the process itself ran into trouble.
Once they are understood as a connected event stream rather than separate API items, QProcess becomes much easier to reason about in real applications.