process-api-net11

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Process API Improvements — .NET 11

Process API 改进 — .NET 11

New APIs added to
System.Diagnostics.Process
in .NET 11 simplify process management, eliminate boilerplate, and prevent common deadlock patterns when capturing output in .NET 11 or later applications.
.NET 11 中为
System.Diagnostics.Process
新增的 API 简化了进程管理,减少了样板代码,并可防止在 .NET 11 或更高版本应用中捕获输出时常见的死锁模式。

When to Use

使用时机

  • Running or orchestrating external processes in a .NET 11 (or later) project.
  • Needing to start a process, wait for it to exit, and capture its output/error streams without risking deadlocks (
    Process.RunAndCaptureText[Async]
    ).
  • Wanting to ensure child processes are automatically terminated when the parent process exits (
    KillOnParentExit
    ).
  • Requiring lightweight, low-overhead process creation via
    SafeProcessHandle
    on supported platforms.
  • Requiring fine-grained control over handle inheritance (
    InheritedHandles
    ) or starting detached processes (
    StartDetached
    ).
  • 在 .NET 11(或更高版本)项目中运行或编排外部进程。
  • 需要启动进程、等待其退出并捕获其输出和错误流,同时避免死锁风险(
    Process.RunAndCaptureText[Async]
    )。
  • 希望确保父进程退出时自动终止子进程(
    KillOnParentExit
    )。
  • 需要在受支持的平台上通过
    SafeProcessHandle
    进行轻量级、低开销的进程创建。
  • 需要精确控制句柄继承(
    InheritedHandles
    )或启动分离进程(
    StartDetached
    )。

When Not to Use

不适用场景

  • The project targets .NET 10 or earlier — these APIs are not available before .NET 11.
  • The default
    Process.Start()
    is sufficient and does not require output capturing or advanced lifecycle rules.
  • 项目面向 .NET 10 或更早版本——这些 API 在 .NET 11 之前不可用。
  • 默认的
    Process.Start()
    已经足够,且不需要捕获输出或使用高级生命周期规则。

Target Framework

目标框架

xml
<TargetFramework>net11.0</TargetFramework>
xml
<TargetFramework>net11.0</TargetFramework>

New APIs

新 API

Types

类型

Before using the new convenience methods, note the following types. Use the framework types directly:
TypeKindRead-only properties
ProcessExitStatus
Sealed class
int ExitCode
,
bool Canceled
,
PosixSignal? Signal
ProcessTextOutput
Sealed class
ProcessExitStatus ExitStatus
,
string StandardOutput
,
string StandardError
,
int ProcessId
ProcessOutputLine
Readonly struct
string Content
,
bool StandardError
ProcessExitStatus
describes the outcome of a completed process. Interpret
ExitCode
using the external command's contract; there is no universal
Success
property.
ProcessTextOutput
contains the exit status, captured output and error text, and process ID.
ProcessOutputLine
identifies one output line and whether it came from standard error.
使用新的便捷方法之前,请注意以下类型。直接使用框架提供的类型:
类型类别只读属性
ProcessExitStatus
密封类
int ExitCode
、
bool Canceled
、
PosixSignal? Signal
ProcessTextOutput
密封类
ProcessExitStatus ExitStatus
、
string StandardOutput
、
string StandardError
、
int ProcessId
ProcessOutputLine
只读结构体
string Content
、
bool StandardError
ProcessExitStatus
描述已完成进程的执行结果。应根据外部命令的约定解释
ExitCode
;不存在通用的
Success
属性。
ProcessTextOutput
包含退出状态、捕获的输出和错误文本以及进程 ID。
ProcessOutputLine
标识一行输出,并指出该行是否来自标准错误流。

High-Level Convenience APIs

高级便捷 API

Static Methods

静态方法

The
ProcessStartInfo
overloads below require
UseShellExecute = false
, which is the default for .NET.
以下
ProcessStartInfo
重载要求
UseShellExecute = false
,这是 .NET 的默认值。
Process.Run
/
Process.RunAsync
Process.Run
/
Process.RunAsync
Starts a process and waits for it to exit, returning the exit status. Does not capture standard output or error. Passing
silent: true
discards standard output and error by internally redirecting standard handles to the
NUL
device. On timeout or cancellation, the process is killed.
csharp
public static ProcessExitStatus Run(string fileName, IEnumerable<string>? arguments = null, bool silent = false, TimeSpan? timeout = null)
public static Task<ProcessExitStatus> RunAsync(string fileName, IEnumerable<string>? arguments = null, bool silent = false, CancellationToken cancellationToken = default)
public static ProcessExitStatus Run(ProcessStartInfo startInfo, TimeSpan? timeout = null)
public static Task<ProcessExitStatus> RunAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default)
启动进程并等待其退出,返回退出状态。不捕获标准输出或错误输出。传入
silent: true
时,会在内部将标准句柄重定向到
NUL
设备,从而丢弃标准输出和错误输出。发生超时或取消时,进程会被终止。
csharp
public static ProcessExitStatus Run(string fileName, IEnumerable<string>? arguments = null, bool silent = false, TimeSpan? timeout = null)
public static Task<ProcessExitStatus> RunAsync(string fileName, IEnumerable<string>? arguments = null, bool silent = false, CancellationToken cancellationToken = default)
public static ProcessExitStatus Run(ProcessStartInfo startInfo, TimeSpan? timeout = null)
public static Task<ProcessExitStatus> RunAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default)
Process.RunAndCaptureText
/
Process.RunAndCaptureTextAsync
Process.RunAndCaptureText
/
Process.RunAndCaptureTextAsync
Starts a process, captures both standard output and error, and waits for it to exit. Extremely useful for avoiding deadlocks on stream redirection. On timeout or cancellation, the process is killed.
When using the
ProcessStartInfo
overloads, the caller is responsible for setting
RedirectStandardOutput = true
and
RedirectStandardError = true
on
ProcessStartInfo
(because BCL APIs cannot modify the input arguments they were given).
csharp
public static ProcessTextOutput RunAndCaptureText(string fileName, IEnumerable<string>? arguments = null, TimeSpan? timeout = null)
public static Task<ProcessTextOutput> RunAndCaptureTextAsync(string fileName, IEnumerable<string>? arguments = null, CancellationToken cancellationToken = default)
public static ProcessTextOutput RunAndCaptureText(ProcessStartInfo startInfo, TimeSpan? timeout = null)
public static Task<ProcessTextOutput> RunAndCaptureTextAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default)
启动进程、同时捕获标准输出和错误输出,并等待其退出。对于避免流重定向导致的死锁极为有用。发生超时或取消时,进程会被终止。
使用
ProcessStartInfo
重载时,调用方负责在
ProcessStartInfo
上设置
RedirectStandardOutput = true
和
RedirectStandardError = true
(因为 BCL API 无法修改传入的参数)。
csharp
public static ProcessTextOutput RunAndCaptureText(string fileName, IEnumerable<string>? arguments = null, TimeSpan? timeout = null)
public static Task<ProcessTextOutput> RunAndCaptureTextAsync(string fileName, IEnumerable<string>? arguments = null, CancellationToken cancellationToken = default)
public static ProcessTextOutput RunAndCaptureText(ProcessStartInfo startInfo, TimeSpan? timeout = null)
public static Task<ProcessTextOutput> RunAndCaptureTextAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default)
Process.StartAndForget
Process.StartAndForget
There is a common misconception that when a process is disposed, it's also being killed. This is not the case, as
Process.Dispose
only releases the resources associated with the process, but does not kill it.
To make it easier to start a process without the need to worry about disposing it,
Process.StartAndForget
was introduced. The method starts an executable, returns its ID, and immediately releases all handle resources associated with it. Standard handles not supplied through
StandardInputHandle
,
StandardOutputHandle
, or
StandardErrorHandle
go to the null device by default.
StartAndForget
throws
InvalidOperationException
if
UseShellExecute
or any
RedirectStandardInput
,
RedirectStandardOutput
, or
RedirectStandardError
flag is true. Use a capture or streaming API if output needs to be read.
csharp
public static int StartAndForget(string fileName, IEnumerable<string>? arguments = null)
public static int StartAndForget(ProcessStartInfo startInfo)
一种常见误解是,释放进程对象时也会将其终止。实际上并非如此,因为
Process.Dispose
只会释放与进程关联的资源,而不会终止进程。
为了更容易地在无需考虑释放进程的情况下启动进程,引入了
Process.StartAndForget
。该方法启动可执行文件,返回其 ID,并立即释放与其关联的所有句柄资源。未通过
StandardInputHandle
、
StandardOutputHandle
或
StandardErrorHandle
提供的标准句柄默认会转到 null 设备。
如果
UseShellExecute
或任意
RedirectStandardInput
、
RedirectStandardOutput
、
RedirectStandardError
标志为 true,
StartAndForget
将抛出
InvalidOperationException
。如果需要读取输出,请使用捕获或流式 API。
csharp
public static int StartAndForget(string fileName, IEnumerable<string>? arguments = null)
public static int StartAndForget(ProcessStartInfo startInfo)

Instance Methods

实例方法

These methods are called on a
Process
instance to directly read stdout and stderr, guaranteeing no OS pipe buffer overflow deadlocks.
Note: Calling these methods requires
RedirectStandardOutput = true
and
RedirectStandardError = true
on the
ProcessStartInfo
passed to
Process.Start
.
csharp
public (string StandardOutput, string StandardError) ReadAllText(TimeSpan? timeout = null)
public Task<(string StandardOutput, string StandardError)> ReadAllTextAsync(CancellationToken cancellationToken = default)
public (byte[] StandardOutput, byte[] StandardError) ReadAllBytes(TimeSpan? timeout = null)
public Task<(byte[] StandardOutput, byte[] StandardError)> ReadAllBytesAsync(CancellationToken cancellationToken = default)
public IEnumerable<ProcessOutputLine> ReadAllLines(TimeSpan? timeout = null)
public IAsyncEnumerable<ProcessOutputLine> ReadAllLinesAsync(CancellationToken cancellationToken = default)
Example of deconstructing the tuple return:
csharp
var (stdout, stderr) = await process.ReadAllTextAsync();
这些方法在
Process
实例上调用,用于直接读取 stdout 和 stderr,并保证不会因操作系统管道缓冲区溢出而死锁。
注意:调用这些方法时,必须在传给
Process.Start
的
ProcessStartInfo
上设置
RedirectStandardOutput = true
和
RedirectStandardError = true
。
csharp
public (string StandardOutput, string StandardError) ReadAllText(TimeSpan? timeout = null)
public Task<(string StandardOutput, string StandardError)> ReadAllTextAsync(CancellationToken cancellationToken = default)
public (byte[] StandardOutput, byte[] StandardError) ReadAllBytes(TimeSpan? timeout = null)
public Task<(byte[] StandardOutput, byte[] StandardError)> ReadAllBytesAsync(CancellationToken cancellationToken = default)
public IEnumerable<ProcessOutputLine> ReadAllLines(TimeSpan? timeout = null)
public IAsyncEnumerable<ProcessOutputLine> ReadAllLinesAsync(CancellationToken cancellationToken = default)
解构元组返回值的示例:
csharp
var (stdout, stderr) = await process.ReadAllTextAsync();

ProcessStartInfo Properties

ProcessStartInfo 属性

KillOnParentExit

KillOnParentExit

Ensures that the spawned child process is terminated when the current (parent) process exits (including fatal crash and being force killed). Works across Windows, Linux, and Android.
csharp
public bool KillOnParentExit { get; set; }
In cross-platform code, guard the property assignment with a supported-platform check, not only the assigned value. If automatic teardown is required, fail explicitly on an unsupported platform instead of starting an unprotected child process.
KillOnParentExit
requires
UseShellExecute = false
.
确保生成的子进程在当前(父)进程退出时被终止(包括致命崩溃和被强制终止)。支持 Windows、Linux 和 Android。
csharp
public bool KillOnParentExit { get; set; }
在跨平台代码中,应通过受支持的平台检查来保护属性赋值,而不能只检查所赋的值。如果需要自动清理,则应在不支持的平台上显式失败,而不是启动一个未受保护的子进程。
KillOnParentExit
要求
UseShellExecute = false
。

InheritedHandles

InheritedHandles

Provides precise control over which handles (file descriptors) are inherited by the child process, preventing accidental resource leaks.
  • Standard handles (
    stdin
    ,
    stdout
    ,
    stderr
    ) are always included (no need to add them to the list).
  • Setting the list to an empty list means only standard handles get inherited.
  • Only
    SafeFileHandle
    and
    SafePipeHandle
    instances are allowed as of today.
  • No global lock is used when spawning new processes on Windows (important for tuning projects that spawn multiple processes in parallel).
  • Concurrent process starts must not pass the same handle in
    InheritedHandles
    : the runtime temporarily changes its inheritance flags. Serialize starts that share a handle, or use separate handles for each concurrent start.
  • Do not enable inheritance on the handles before passing them; other process-start APIs could then inherit them unintentionally.
  • On Unix systems without native handle-inheritance control, setting this property can severely reduce process-start performance.
  • A non-null list requires
    UseShellExecute = false
    and cannot be combined with a non-empty
    UserName
    .
csharp
public IList<SafeHandle>? InheritedHandles { get; set; }
精确控制由子进程继承哪些句柄(文件描述符),防止意外的资源泄漏。
  • 标准句柄(
    stdin
    、
    stdout
    、
    stderr
    )始终会被继承(无需将其添加到列表中)。
  • 将列表设置为空列表表示仅继承标准句柄。
  • 目前只允许使用
    SafeFileHandle
    和
    SafePipeHandle
    实例。
  • 在 Windows 上启动新进程时不会使用全局锁(这对于调优并行启动多个进程的项目非常重要)。
  • 并发启动进程时,不得在
    InheritedHandles
    中传入同一个句柄:运行时会临时更改其继承标志。对于共享句柄的启动操作,应进行串行化,或者为每次并发启动使用单独的句柄。
  • 在传入句柄之前,不要先启用句柄的继承;否则,其他进程启动 API 可能会在无意中继承这些句柄。
  • 在不原生支持句柄继承控制的 Unix 系统上,设置此属性可能会显著降低进程启动性能。
  • 非 null 列表要求
    UseShellExecute = false
    ,并且不能与非空的
    UserName
    组合使用。
csharp
public IList<SafeHandle>? InheritedHandles { get; set; }

StartDetached

StartDetached

Starts the process detached from the parent's terminal or job session, ensuring it survives the parent's exit.
csharp
public bool StartDetached { get; set; }
StartDetached
requires
UseShellExecute = false
.

以脱离父进程终端或作业会话的方式启动进程,确保其在父进程退出后仍能继续运行。
csharp
public bool StartDetached { get; set; }
StartDetached
要求
UseShellExecute = false
。

Examples

示例

1. One-Line Run and Capture Output

1. 一行代码运行并捕获输出

Run a process and safely read all output text without stream deadlock risks. For simple CLI operations that don't need cancellation or async scalability, prefer the synchronous overload:
csharp
using System;
using System.Diagnostics;

// Run 'git status' and capture output
ProcessTextOutput result = Process.RunAndCaptureText("git", ["status"]);

if (result.ExitStatus.ExitCode == 0)
{
    Console.WriteLine($"Git Output: {result.StandardOutput}");
}
else
{
    Console.WriteLine($"Failed with exit code: {result.ExitStatus.ExitCode}");
    Console.WriteLine($"Error: {result.StandardError}");
}
运行进程并安全地读取所有输出文本,不会产生流死锁风险。对于不需要取消或异步扩展能力的简单 CLI 操作,优先使用同步重载:
csharp
using System;
using System.Diagnostics;

// Run 'git status' and capture output
ProcessTextOutput result = Process.RunAndCaptureText("git", ["status"]);

if (result.ExitStatus.ExitCode == 0)
{
    Console.WriteLine($"Git Output: {result.StandardOutput}");
}
else
{
    Console.WriteLine($"Failed with exit code: {result.ExitStatus.ExitCode}");
    Console.WriteLine($"Error: {result.StandardError}");
}

2. Auto-Killing Child Processes on Parent Exit

2. 父进程退出时自动终止子进程

On Windows, Linux, and Android, ensure a long-running background worker process is killed when the main application terminates. On other platforms, this example stops before starting the child process:
csharp
using System;
using System.Diagnostics;

ProcessStartInfo startInfo = new("dotnet", ["run", "--project", "BackgroundWorker.csproj"]);

if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux() || OperatingSystem.IsAndroid())
{
    startInfo.KillOnParentExit = true;
}
else
{
    throw new PlatformNotSupportedException("This example requires Windows, Linux, or Android.");
}

using Process process = Process.Start(startInfo)!;
// The background worker is now tied to this process's lifecycle
在 Windows、Linux 和 Android 上,确保长时间运行的后台工作进程在主应用终止时被终止。在其他平台上,此示例会在启动子进程之前停止执行:
csharp
using System;
using System.Diagnostics;

ProcessStartInfo startInfo = new("dotnet", ["run", "--project", "BackgroundWorker.csproj"]);

if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux() || OperatingSystem.IsAndroid())
{
    startInfo.KillOnParentExit = true;
}
else
{
    throw new PlatformNotSupportedException("This example requires Windows, Linux, or Android.");
}

using Process process = Process.Start(startInfo)!;
// The background worker is now tied to this process's lifecycle

3. Read All Lines From Output

3. 读取所有输出行

Start a process and read its output lines safely:
csharp
using System;
using System.Diagnostics;
using System.Threading.Tasks;

ProcessStartInfo startInfo = new("ping", ["127.0.0.1"])
{
    RedirectStandardOutput = true,
    RedirectStandardError = true
};

using Process process = Process.Start(startInfo)!;
// Read all output lines safely and asynchronously
await foreach (ProcessOutputLine line in process.ReadAllLinesAsync())
{
    string prefix = line.StandardError ? "[Err]" : "[Out]";
    Console.WriteLine($"{prefix} > {line.Content}");
}
启动进程并安全地读取其输出行:
csharp
using System;
using System.Diagnostics;
using System.Threading.Tasks;

ProcessStartInfo startInfo = new("ping", ["127.0.0.1"])
{
    RedirectStandardOutput = true,
    RedirectStandardError = true
};

using Process process = Process.Start(startInfo)!;
// Read all output lines safely and asynchronously
await foreach (ProcessOutputLine line in process.ReadAllLinesAsync())
{
    string prefix = line.StandardError ? "[Err]" : "[Out]";
    Console.WriteLine($"{prefix} > {line.Content}");
}

4. Read Entire Output Deadlock-Free (Tuple Return)

4. 无死锁地读取全部输出(元组返回)

Start a process with custom
ProcessStartInfo
and read both stdout and stderr into a deconstructed tuple without buffer deadlocks:
csharp
using System;
using System.Diagnostics;
using System.Threading.Tasks;

ProcessStartInfo startInfo = new("git", ["diff"])
{
    RedirectStandardOutput = true,
    RedirectStandardError = true
};

using Process process = Process.Start(startInfo)!;
var (stdout, stderr) = await process.ReadAllTextAsync();

Console.WriteLine($"Diff output: {stdout}");
使用自定义
ProcessStartInfo
启动进程,并将 stdout 和 stderr 读取到可解构的元组中,避免缓冲区死锁:
csharp
using System;
using System.Diagnostics;
using System.Threading.Tasks;

ProcessStartInfo startInfo = new("git", ["diff"])
{
    RedirectStandardOutput = true,
    RedirectStandardError = true
};

using Process process = Process.Start(startInfo)!;
var (stdout, stderr) = await process.ReadAllTextAsync();

Console.WriteLine($"Diff output: {stdout}");

5. Start and Forget (Fire & Forget)

5. 启动后不再跟踪(Fire & Forget)

Launch a helper executable without holding onto system handle structures:
csharp
using System;
using System.Diagnostics;

// Fire and forget, getting back only the process ID
int pid = Process.StartAndForget("notepad.exe");
Console.WriteLine($"Notepad started with PID: {pid}");
启动辅助可执行程序,无需持有系统句柄结构:
csharp
using System;
using System.Diagnostics;

// Fire and forget, getting back only the process ID
int pid = Process.StartAndForget("notepad.exe");
Console.WriteLine($"Notepad started with PID: {pid}");