C#调用带Awk管道的CMD命令时进程无法退出如何解决
C# 调用Cmd执行带管道命令时进程卡死无法退出修复
问题相关代码
public class CmdHelper { public static string StartCmd(string commandLine) { commandLine = commandLine.Trim().TrimStart('&') + "&exit"; string outputMsg = ""; Process pro = new Process(); pro.StartInfo.FileName = "cmd.exe"; pro.StartInfo.UseShellExecute = false; pro.StartInfo.RedirectStandardError = true; pro.StartInfo.RedirectStandardInput = true; pro.StartInfo.RedirectStandardOutput = true; pro.StartInfo.CreateNoWindow = false; pro.Start(); pro.StandardInput.WriteLine(commandLine); pro.StandardInput.AutoFlush = true; pro.StartInfo.StandardErrorEncoding = Encoding.UTF8; pro.StartInfo.StandardOutputEncoding = Encoding.UTF8; outputMsg += pro.StandardOutput.ReadToEnd(); pro.WaitForExit(); pro.Close(); return outputMsg; } public static void CommitBundleToSvn() { string folderPath = EditorConst.BundlesPath + VEngine.Editor.Builds.Settings.GetPlatformName(); string command = "svn status " + folderPath; string regaxPattern = ".*&exit"; string output = ""; string[] regexRes = {}; command = "svn st " + folderPath+ " | awk \"{if ($1 == \\\"?\\\") {print $2} }\""; output = CmdHelper.StartCmd(command); UnityEngine.Debug.LogError(command); UnityEngine.Debug.LogError(output); } }
执行的命令如下:
svn st <目标文件夹路径> | awk "{if ($1 == \"?\") {print $2} }"
现象说明
- 上述命令直接在Windows Cmd窗口手动执行时,可以正常获取返回结果
- 代码中调用
CommitBundleToSvn()方法时,Cmd进程持续挂起无法正常退出 - 将执行命令替换为不带管道的
svn status <目标文件夹路径>时,程序运行表现符合预期
问题根因
代码存在三处隐患,带管道的命令执行时会触发问题:
- 编码设置时机错误:
StandardErrorEncoding、StandardOutputEncoding属性必须在调用Process.Start()前赋值,进程启动后修改这两个属性不会生效,会导致流读取编码异常、阻塞。 - 标准流死锁:同时重定向标准输出、标准错误流时,同步调用
ReadToEnd()读取其中一个流,如果另一个流的缓冲区被子进程写满(默认缓冲区约4KB),子进程会阻塞在写流操作上,主进程则阻塞在等待子进程退出/读流完成,形成死锁。不带管道的svn status输出量小,没有触发缓冲区写满,所以碰巧运行正常。 - 输入流未关闭:向标准输入写入命令后没有关闭输入流,管道右侧的
awk命令会持续等待后续输入,不会主动结束执行。
修复方案
调整StartCmd方法逻辑,修正配置顺序、关闭输入流、用异步读取避免死锁,修复后代码如下:
public static string StartCmd(string commandLine) { commandLine = commandLine.Trim().TrimStart('&') + "&exit"; string outputMsg = ""; Process pro = new Process(); pro.StartInfo.FileName = "cmd.exe"; pro.StartInfo.UseShellExecute = false; pro.StartInfo.RedirectStandardError = true; pro.StartInfo.RedirectStandardInput = true; pro.StartInfo.RedirectStandardOutput = true; pro.StartInfo.CreateNoWindow = false; // 所有StartInfo配置必须在Start调用前完成 pro.StartInfo.StandardErrorEncoding = Encoding.UTF8; pro.StartInfo.StandardOutputEncoding = Encoding.UTF8; pro.Start(); // 写入命令后关闭标准输入,通知子进程输入已结束 pro.StandardInput.WriteLine(commandLine); pro.StandardInput.AutoFlush = true; pro.StandardInput.Close(); // 异步同时读取两个流,避免死锁 Task<string> readOutputTask = pro.StandardOutput.ReadToEndAsync(); Task<string> readErrorTask = pro.StandardError.ReadToEndAsync(); pro.WaitForExit(); outputMsg = readOutputTask.Result; string errorMsg = readErrorTask.Result; if (!string.IsNullOrEmpty(errorMsg)) { UnityEngine.Debug.LogError($"命令执行错误输出: {errorMsg}"); } pro.Close(); return outputMsg; }
如果不需要捕获错误输出,也可以直接将pro.StartInfo.RedirectStandardError设为false,不重定向错误流,也可以规避流死锁问题。
内容的提问来源于stack exchange,提问作者Tcyily
相关产品推荐
相关产品推荐

