当前位置: 首页 > news >正文

.NET 11 中 Process API 升级

.NET 11 中 Process API 升级

Intro

.NET 11 Preview 4 中引入了一系列新的 API ,在调用命令行程序时变得更加方便了。

在 .NET 世界里,System.Diagnostics.Process一直是个“能用,但不优雅”的 API。

它功能很强,但同时也:

  • • 容易死锁

  • • 配置繁琐

  • • 生命周期管理麻烦

  • • 输出捕获容易踩坑

  • • NativeAOT 不友好

现在,.NET 11 终于对 Process API 动刀了,而且这次不是小修小补,而是一次“多年未见”的大升级。

New API

namespace Microsoft.Win32.SafeHandles { public sealed class SafeProcessHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { + public void Kill(); + public bool Signal(System.Runtime.InteropServices.PosixSignal signal); + public static Microsoft.Win32.SafeHandles.SafeProcessHandle Start(System.Diagnostics.ProcessStartInfo startInfo); + public bool TryWaitForExit(System.TimeSpan timeout, out System.Diagnostics.ProcessExitStatus? exitStatus); + public System.Diagnostics.ProcessExitStatus WaitForExit(); + public System.Threading.Tasks.Task<System.Diagnostics.ProcessExitStatus> WaitForExitAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public System.Threading.Tasks.Task<System.Diagnostics.ProcessExitStatus> WaitForExitOrKillOnCancellationAsync(System.Threading.CancellationToken cancellationToken); + public System.Diagnostics.ProcessExitStatus WaitForExitOrKillOnTimeout(System.TimeSpan timeout); + public int ProcessId { get; } } } namespace System.Diagnostics { public class Process : System.ComponentModel.Component, System.IDisposable { + public (byte[] StandardOutput, byte[] StandardError) ReadAllBytes(System.TimeSpan? timeout = null); + public System.Threading.Tasks.Task<(byte[] StandardOutput, byte[] StandardError)> ReadAllBytesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public System.Collections.Generic.IAsyncEnumerable<System.Diagnostics.ProcessOutputLine> ReadAllLinesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public (string StandardOutput, string StandardError) ReadAllText(System.TimeSpan? timeout = null); + public System.Threading.Tasks.Task<(string StandardOutput, string StandardError)> ReadAllTextAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public static System.Diagnostics.ProcessExitStatus Run(System.Diagnostics.ProcessStartInfo startInfo, System.TimeSpan? timeout = null); + public static System.Diagnostics.ProcessExitStatus Run(string fileName, System.Collections.Generic.IList<string>? arguments = null, System.TimeSpan? timeout = null); + public static System.Diagnostics.ProcessTextOutput RunAndCaptureText(System.Diagnostics.ProcessStartInfo startInfo, System.TimeSpan? timeout = null); + public static System.Diagnostics.ProcessTextOutput RunAndCaptureText(string fileName, System.Collections.Generic.IList<string>? arguments = null, System.TimeSpan? timeout = null); + public static System.Threading.Tasks.Task<System.Diagnostics.ProcessTextOutput> RunAndCaptureTextAsync(System.Diagnostics.ProcessStartInfo startInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public static System.Threading.Tasks.Task<System.Diagnostics.ProcessTextOutput> RunAndCaptureTextAsync(string fileName, System.Collections.Generic.IList<string>? arguments = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public static System.Threading.Tasks.Task<System.Diagnostics.ProcessExitStatus> RunAsync(System.Diagnostics.ProcessStartInfo startInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public static System.Threading.Tasks.Task<System.Diagnostics.ProcessExitStatus> RunAsync(string fileName, System.Collections.Generic.IList<string>? arguments = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public static int StartAndForget(System.Diagnostics.ProcessStartInfo startInfo); + public static int StartAndForget(string fileName, System.Collections.Generic.IList<string>? arguments = null); } public sealed class ProcessStartInfo { + public System.Collections.Generic.IList<System.Runtime.InteropServices.SafeHandle>? InheritedHandles { get; set; } + public bool KillOnParentExit { get; set; } + public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardErrorHandle { get; set; } + public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardInputHandle { get; set; } + public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardOutputHandle { get; set; } + public bool StartDetached { get; set; } } + public readonly struct ProcessOutputLine + { + public ProcessOutputLine(string content, bool standardError); + public string Content { get; } + public bool StandardError { get; } + } + public sealed class ProcessTextOutput + { + public ProcessTextOutput(System.Diagnostics.ProcessExitStatus exitStatus, string standardOutput, string standardError, int processId); + public System.Diagnostics.ProcessExitStatus ExitStatus { get; } + public int ProcessId { get; } + public string StandardError { get; } + public string StandardOutput { get; } + } }

Sample

Run

Process类型新增了Run/RunAsync静态方法,不需要再先实例化 Process 就可以直接调用命令行程序,示例如下:

var result = Process.Run("dotnet", ["--version"]); ConsoleHelper.WriteLineWithColor(JsonSerializer.Serialize(result), ConsoleColor.Green); result = await Process.RunAsync("dotnet", ["--info"]); ConsoleHelper.WriteLineWithColor(JsonSerializer.Serialize(result), ConsoleColor.Green); result = await Process.RunAsync(new ProcessStartInfo("dotnet", "--info"), ApplicationHelper.ExitToken); ConsoleHelper.WriteLineWithColor(JsonSerializer.Serialize(result), ConsoleColor.Green); using var canceledCts = new CancellationTokenSource(); canceledCts.Cancel(); result = await Process.RunAsync(new ProcessStartInfo("dotnet", "--info"), canceledCts.Token); ConsoleHelper.WriteLineWithColor(JsonSerializer.Serialize(result), ConsoleColor.Green);

Process.Run/Process.RunAsync在执行命令时也会输出对应的命令行输出

`Run`

`Run 2`

`Run 3`

Run/RunAsync也支持超时和取消, 它的返回值是一个新加的ProcessExitStatus

public sealed class ProcessExitStatus { public int ExitCode { get; } public PosixSignal? Signal { get; } public bool Canceled { get; } }

当 CancellationToken 取消的时候,返回的结果里的Canceled就是true就像上面最后一个示例所示

StartAndForget

新增了Process.StartAndForgetAPI,借助这个 API 可以方便地启动一个 Process 并忽略它的输出,对于不关心输出的场景比较适用,比如只是启动记事本 notebook 或者 code 等,示例如下:

var processId = Process.StartAndForget("code"); Console.WriteLine($"ProcessId: {processId}");

ReadlAllText/ReadAllBytes/ReadAllLinesAsync

Process 新增了 ReadAllText/ReadAllBytes 方法来方便地获取 Process 的输出

{ // ReadAllText using var process = Process.Start(new ProcessStartInfo("dotnet", "--info") { RedirectStandardOutput = true, RedirectStandardError = true, }); Debug.Assert(process is not null); var output = process.ReadAllText(); ConsoleHelper.WriteLineWithColor(output.StandardOutput, ConsoleColor.Green); ConsoleHelper.WriteLineWithColor(output.StandardError, ConsoleColor.Red); } { // ReadAllBytesAsync using var process = Process.Start(new ProcessStartInfo("dotnet", "--info") { RedirectStandardOutput = true, RedirectStandardError = true, }); Debug.Assert(process is not null); var output = await process.ReadAllBytesAsync(); ConsoleHelper.WriteLineWithColor(output.StandardOutput.Length.ToString(), ConsoleColor.Green); ConsoleHelper.WriteLineWithColor(output.StandardError.Length.ToString(), ConsoleColor.Red); }

对于输出较多较久的情况可以考虑使用ReadAllLinesAsync来流式地读取输出,可以参考下面的示例

using var process = Process.Start(new ProcessStartInfo("dotnet", "--info") { RedirectStandardOutput = true, RedirectStandardError = true, }); Debug.Assert(process is not null); await foreach (var line in process.ReadAllLinesAsync()) { ConsoleHelper.WriteLineWithColor(line.Content, line.StandardError ? ConsoleColor.Red : ConsoleColor.Green); }

流式返回的结果也是新增的 API -ProcessOutputLine

public readonly struct ProcessOutputLine { public string Content { get; } public bool StandardError { get; } }

Content是输出的内容,而StandardError表示该内容是来自标准输出(StandardOutput)还是来自错误输出(StandardError)

RunAndCapture

前面的几种读取输出方式还是要先实例化 Process 并显式声明重定向输出,相对来说还是稍微麻烦一些

那就可以考虑下Process.RunAndCapture/Process.RunAndCaptureAsync方法,这个方法在Run/RunAsync的基础之上将输出结果不直接输出到 Console 收集到返回值中,示例如下:

var result = Process.RunAndCaptureText("dotnet", ["--version"]); ConsoleHelper.WriteLineWithColor(JsonSerializer.Serialize(result), ConsoleColor.Green); result = await Process.RunAndCaptureTextAsync("dotnet", ["--info"]); ConsoleHelper.WriteLineWithColor(JsonSerializer.Serialize(result), ConsoleColor.Green);

`Process.RunAndCapture`/`Process.RunAndCaptureAsync`

Process.RunAndCapture/Process.RunAndCaptureAsync方法对于获取输出结果非常的方便,但是感觉缺少了对应的ReadAllLinesAsync对应的简洁方法,提了一个 issue 希望能增加一个对应的 APIProcess.RunAndCaptureLinesAsync可以参考:https://github.com/dotnet/runtime/issues/128280

namespace System.Diagnostics; public class Process { public static IAsyncEnumerable<ProcessOutputLine> RunAndCaptureLinesAsync(string fileName, IList<string>? arguments = null, CancellationToken cancellationToken = default); public static IAsyncEnumerable<ProcessOutputLine> RunAndCaptureLinesAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default); }

使用示例:

await foreach (var line in Process.RunAndCaptureLinesAsync("dotnet", ["--info"])) { ConsoleHelper.WriteLineWithColor(line.Content, line.StandardError ? ConsoleColor.Red : ConsoleColor.Green); }

大家觉得有帮助的话可以帮忙点个赞,希望在 .NET 11 可以一起添加上去

More

子进程生命周期管理终于现代化了

这部分其实是企业级场景最重要的升级。

新增:

KillOnParentExit

父进程退出时,自动终止子进程。

new ProcessStartInfo { KillOnParentExit = true };

这个功能在:

  • • IDE

  • • CLI Tool

  • • Agent Runtime

  • • Build System

  • • Game Launcher

  • • Electron + .NET Hybrid App

里极其重要。

因为以前大量僵尸进程就是:

  • • 主程序崩了

  • • 子进程还活着

现在官方终于补齐了。

StartDetached

允许真正 detached child process。

适合:

  • • daemon

  • • service bootstrap

  • • local AI runtime

  • • background updater

对于 Agent 场景尤其关键

NativeAOT

新的 Process API 引入了SafeProcessHandle以前ProcessAPI 太重对于 AOT 并不友好,现在更加轻量、对于 AOT 更加友好,对于性能的提升也非常显著,并且有着更少的内存占用,可以参考官方博客的介绍 https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11

References

  • • https://github.com/WeihanLi/SamplesInPractice/blob/main/net11sample/Net11Samples/ProcessSamples.cs

  • • https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/

  • • https://github.com/dotnet/runtime/pull/126942

  • • https://github.com/dotnet/runtime/pull/127210

  • • https://github.com/dotnet/runtime/pull/125848

  • • https://github.com/dotnet/runtime/pull/126078

  • • https://github.com/dotnet/runtime/issues/125838

  • • https://github.com/dotnet/core/pull/10403

http://www.jsqmd.com/news/849907/

相关文章:

  • 昆明二手手机专卖店实测:这些机型性价比最高
  • 别再死记FPN公式了!用PyTorch手把手带你画一遍特征金字塔的‘数据流图’
  • 5步掌握ExtractorSharp:游戏资源编辑的终极免费指南
  • Hermes Agent 四层记忆架构中 nudge_interval 主动触发的 4 种典型场景与间隔设置策略
  • Claude Code API 接入实测:Anthropic 直连、OpenRouter 与第三方代理的 3 种路径合规性及稳定性对比
  • 从图片到声音、视频:MaxCompute MaxFrame 多模态算子模块,让海量多模态数据_跑_起来
  • 数字化时代,企业线下营销物料为何依然不可替代?
  • 5 分钟原型验证实战:Trae 在极速开发工作流中的 4 种快速试错策略
  • BYOK 模式下节省 37% API 成本:Cursor 工程配置的 4 类密钥路由策略
  • 如何配置多层 SSH 隧道代理链实现跨网段访问?
  • STC89C52RC+HX711:手把手教你做一个5KG高精度电子秤(附语音播报模块选型避坑)
  • 从零想法到可部署 MVP:v0 + Cursor + Vibe Coding 三步工作流实战
  • 别再死记硬背模型了!用Meta-Learning让AI学会‘举一反三’,5分钟看懂小样本学习核心
  • 闪灯电路板
  • C51多任务环境下数据覆盖问题的解决方案
  • 破局京城老酒变现困局 京城亚南酒业,以高效诚信守护藏家权益 - 品牌排行榜单
  • 如何快速让经典Windows游戏焕发新生:DDrawCompat终极指南
  • 鸿蒙ArkUI自定义视频播放器开发实战:从AVPlayer到手势交互全解析
  • Claude Code 的 Token 限额配置:3 种超限熔断策略与成本告警设置指南
  • 别再死记UNet结构了!用‘编码-解码’与‘特征融合’视角重新理解注意力与残差
  • 别再让死区拖慢你的电机!STM32+Simulink自动生成代码,手把手教你搞定死区补偿(附Keil在线标定技巧)
  • 嵌入式Linux启动时间从20秒优化至5秒:i.MX 8M Mini系统级实战
  • 嵌入式系统入门指南:从零基础到实践应用
  • 写完一个 AI 编程助手之后,我才确定 prompt 工程不是重点
  • 7.2 节实战指南:Cursor 中 5 类开发任务对应的最优模型切换策略
  • 文件批量整理效率提升3倍:Trae 在轻量化自动化任务中的 4 种批处理模式
  • 新手必看:用SUMO从零搭建高速公路交通流模型(附完整配置文件)
  • 2026技术趋势:大模型“记忆来源”功能实测,GPT-5.5如何让回答有据可查
  • Gemini 多语言测评:中文/英文的语义保真与表达差异
  • GPT5.5对决Gemini3.1Pro多模态能力全方位实测对比