用途
该技能面向在 .NET 11 或更高版本项目中运行或编排外部进程的场景,介绍 System.Diagnostics.Process 新增的一组 API。这些 API 旨在简化进程管理、消除样板代码,并防止在捕获输出时出现常见的死锁模式。
主要能力
新增类型
- ProcessExitStatus:密封类,只读属性包括 int ExitCode、bool Canceled、PosixSignal? Signal,用于描述已完成进程的结果。ExitCode 需按外部命令的约定解释,没有通用的 Success 属性。
- ProcessTextOutput:密封类,只读属性包括 ProcessExitStatus ExitStatus、string StandardOutput、string StandardError、int ProcessId。
- ProcessOutputLine:只读结构体,属性包括 string Content、bool StandardError,用于标识一行输出及其是否来自标准错误。
高层便捷 API(静态方法)
以下 ProcessStartInfo 重载要求 UseShellExecute = false(.NET 默认值)。
- Process.Run / Process.RunAsync:启动进程并等待退出,返回退出状态,不捕获标准输出或错误。传入 silent: true 会通过将标准句柄重定向到 NUL 设备来丢弃输出。超时或取消时进程会被终止。
- Process.RunAndCaptureText / Process.RunAndCaptureTextAsync:启动进程,捕获标准输出和错误,并等待退出,可避免流重定向死锁。使用 ProcessStartInfo 重载时,调用方需自行设置 RedirectStandardOutput = true 和 RedirectStandardError = true。超时或取消时进程会被终止。
- Process.StartAndForget:启动可执行文件,返回其 ID,并立即释放所有相关句柄资源。未通过 StandardInputHandle、StandardOutputHandle 或 StandardErrorHandle 提供的标准句柄默认指向空设备。若 UseShellExecute 或任何 RedirectStandardInput/Output/Error 标志为 true,会抛出 InvalidOperationException。
实例方法
在 Process 实例上调用,直接读取 stdout 和 stderr,保证不会因 OS 管道缓冲区溢出而死锁。调用前需在传给 Process.Start 的 ProcessStartInfo 上设置 RedirectStandardOutput = true 和 RedirectStandardError = true。
- ReadAllText / ReadAllTextAsync:返回 (string StandardOutput, string StandardError) 元组。
- ReadAllBytes / ReadAllBytesAsync:返回 (byte[] StandardOutput, byte[] StandardError) 元组。
- ReadAllLines / ReadAllLinesAsync:返回 IEnumerable<ProcessOutputLine> 或 IAsyncEnumerable<ProcessOutputLine>。
ProcessStartInfo 属性
- KillOnParentExit:确保当前(父)进程退出时(包括致命崩溃和被强制杀死)终止子进程。支持 Windows、Linux 和 Android。要求 UseShellExecute = false。跨平台代码中应检查平台支持,不支持时显式失败。
- InheritedHandles:精确控制子进程继承哪些句柄(文件描述符),防止意外资源泄漏。标准句柄始终包含;空列表表示仅继承标准句柄;目前仅允许 SafeFileHandle 和 SafePipeHandle。Windows 上生成新进程时不使用全局锁。并发启动不得在 InheritedHandles 中传入同一句柄,需序列化或使用独立句柄。不要在传入前启用句柄继承。在无原生句柄继承控制的 Unix 系统上,设置此属性可能严重降低进程启动性能。非空列表要求 UseShellExecute = false,且不能与非空 UserName 组合使用。
- StartDetached:使进程脱离父终端或作业会话启动,确保其在父进程退出后仍存活。要求 UseShellExecute = false。
使用方式
目标框架需为 net11.0。示例包括:
- 一行运行并捕获输出:Process.RunAndCaptureText("git", ["status"]),检查 result.ExitStatus.ExitCode。
- 父进程退出时自动杀死子进程:设置 startInfo.KillOnParentExit = true,并检查平台支持。
- 读取所有输出行:设置 RedirectStandardOutput/Error,使用 await foreach 遍历 process.ReadAllLinesAsync()。
- 无死锁读取全部输出:设置重定向后,var (stdout, stderr) = await process.ReadAllTextAsync()。
- 启动后不管:int pid = Process.StartAndForget("notepad.exe")。
适用场景
- 在 .NET 11 或更高版本项目中运行或编排外部进程。
- 需要启动进程、等待退出并捕获输出/错误流而不冒死锁风险。
- 希望父进程退出时自动终止子进程。
- 需要通过 SafeProcessHandle 在支持的平台上进行轻量、低开销的进程创建。
- 需要对句柄继承进行细粒度控制,或启动分离进程。
限制
- 项目目标为 .NET 10 或更早版本时不可用,这些 API 在 .NET 11 之前不存在。
- 若默认 Process.Start() 已足够,且不需要输出捕获或高级生命周期规则,则无需使用。
- 部分属性有平台限制(如 KillOnParentExit 仅支持 Windows、Linux、Android)。
- 部分属性要求 UseShellExecute = false,或不能与非空 UserName 组合。
- 在无原生句柄继承控制的 Unix 系统上,InheritedHandles 可能严重降低进程启动性能。
- 并发启动共享句柄时需序列化或使用独立句柄。