.NET 7调用SqlPackage重定向StdError遇无效句柄问题排查
在C# .NET 7中调用SqlPackage时StdError重定向的问题与解决
问题现象
在.NET 7应用中通过System.Diagnostics.Process调用SqlPackage.exe时,出现以下异常情况:
- 执行无错误的有效命令(如
/help)时,若同时开启RedirectStandardError,会抛出“发生意外故障:句柄无效”的错误; - 执行存在错误的命令时,却能正常捕获StdError中的错误信息;
- 若关闭StdError重定向,可正常获取有效命令的StdOutput,但无法捕获潜在的错误信息。
原因分析
这个问题的根源是SqlPackage.exe的特殊行为:当命令执行成功且无错误输出时,它会主动关闭标准错误流(StdError)的句柄,但此时.NET的Process组件仍通过BeginErrorReadLine异步监听该流,导致句柄状态不匹配,触发“无效句柄”异常。而当命令有错误时,SqlPackage会正常向StdError写入数据并保持句柄有效,因此不会触发错误。
解决方法
要同时捕获StdOutput和StdError且避免异常,推荐以下两种可靠方案:
方案1:后台同步读取错误流(替代异步事件监听)
放弃BeginErrorReadLine的异步监听,改用后台线程同步读取StdError流,避免异步监听的句柄冲突:
using System.Diagnostics; // 注意:若为控制台应用,需将Main方法改为async Task Main() async Task Main() { var processStartInfo = new ProcessStartInfo { FileName = @"sqlpackage.exe", Arguments = "/help", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = new Process { StartInfo = processStartInfo, EnableRaisingEvents = true }; // 异步监听StdOutput process.OutputDataReceived += (sender, eventArgs) => { if (!string.IsNullOrEmpty(eventArgs.Data)) { Console.WriteLine($"输出:{eventArgs.Data}"); } }; process.Start(); process.BeginOutputReadLine(); // 在后台线程同步读取StdError,避免句柄冲突 var errorReadTask = Task.Run(() => { while (!process.StandardError.EndOfStream) { var errorLine = process.StandardError.ReadLine(); if (!string.IsNullOrEmpty(errorLine)) { Console.WriteLine($"错误:{errorLine}"); } } }); // 等待进程结束和错误流读取完成 process.WaitForExit(); await errorReadTask; Console.WriteLine($"Process completed with exit code: {process.ExitCode}"); }
方案2:禁用异步错误监听,仅在需要时读取
如果你的场景中错误输出较少,也可以先不开启BeginErrorReadLine,而是在进程退出后检查ExitCode,若不为0再读取StdError内容:
using System.Diagnostics; var processStartInfo = new ProcessStartInfo { FileName = @"sqlpackage.exe", Arguments = "/help", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = new Process { StartInfo = processStartInfo }; process.Start(); // 同步读取StdOutput string output = process.StandardOutput.ReadToEnd(); Console.WriteLine($"输出:{output}"); process.WaitForExit(); // 仅当进程异常退出时读取StdError if (process.ExitCode != 0) { string error = process.StandardError.ReadToEnd(); Console.WriteLine($"错误:{error}"); } Console.WriteLine($"Process completed with exit code: {process.ExitCode}");
注意事项
- 若使用同步读取流,需确保读取操作在
WaitForExit前执行,避免进程退出后流被关闭; - 异步读取方案更适合长时间运行的进程,不会阻塞主线程;
- 需确保SqlPackage.exe的路径正确,或已添加到系统环境变量中。
内容的提问来源于stack exchange,提问作者Frederik Madsen
相关产品推荐
相关产品推荐

