C#使用Process.Start自动化执行UDEC程序时ReadToEnd死锁问题
C# 重定向Process标准流执行UDEC程序死锁解决方案
问题场景
- 手动操作逻辑:双击启动
udecConsole2017.exe后,控制台会输出插件加载、版本、授权等启动信息,待输出出现udec>命令提示符时,输入命令call 'D:\Work\202205\20220525\tunnel-for-cmd.txt'即可触发计算,输出计算结果。
启动阶段控制台输出(系统原因导致的乱码可忽略):
--- module2d plugin DFNModule2D loaded. --- module2d plugin GeometryModule2D loaded. --- module2d plugin Convert loaded. U D E C: VERSION 7.00 赏屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯突 ? Universal Distinct Element Code ? 掏屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯凸 ?Copyright (c):Itasca Consulting Group 2017 ? ? ? 韧屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯图 Licensee: Itasca Consulting Group, Inc. Minneapolis, Minnesota USA Options: Barton-Bandis Creep CppUdm Flow Thermal Memory: 4096 MBytes Precision: Double udec>
命令执行阶段输出:
udec>call 'D:\Work\202205\20220525\tunnel-for-cmd.txt' ................ ................ calculation results ................ ................
- 初始自动化实现代码:
try { var psi = new ProcessStartInfo() { UseShellExecute = false, FileName = @"D:\Program Files\ITASCA\UDEC700\Exe64\udecConsole2017.exe", //Arguments = @"call 'D:\Work\202205\20220525\tunnel-for-cmd.txt'", RedirectStandardInput = true, RedirectStandardOutput = true, CreateNoWindow = true, }; var p = System.Diagnostics.Process.Start(psi); // Read until the udec> prompt while (true) { //var line = p.StandardOutput.ReadLine(); //if (line.StartsWith("udec>")) // break; int v = p.StandardOutput.Read(); Console.Write((char)v); if (v == 62) { break; } if (v == -1) break; } // Write the command Console.Write(@"call 'D:\Work\202205\20220525\tunnel-for-cmd.txt'"); p.StandardInput.WriteLine(@"call 'D:\Work\202205\20220525\tunnel-for-cmd.txt'"); p.StandardInput.Flush(); // Read the result string content = p.StandardOutput.ReadToEnd(); p.WaitForExit(); Console.WriteLine(content); } catch (Exception e) { Console.WriteLine(e.Message); }
- 故障表现:代码运行后可正常输出UDEC启动信息,成功识别
udec>提示符并写入执行命令,但执行到string content = p.StandardOutput.ReadToEnd();语句时程序会挂起死锁、无响应,尝试异步读取、绑定OutputDataReceived事件调用BeginOutputReadLine()等方案均无法解决。
问题根因
死锁核心原因有两点:
ReadToEnd()方法的返回条件是目标进程关闭标准输出流句柄,但UDEC是交互式控制台程序,执行完传入的call命令后不会退出进程,会重新回到udec>提示符等待下一条指令,标准输出流会一直保持打开状态,ReadToEnd()会无限阻塞等待流关闭。- 原逻辑中判断提示符仅判断了字符
>(ASCII码62),如果启动日志或计算结果中出现>字符会提前跳出读取循环,残留未读的缓冲区内容也会增加阻塞概率。
修复方案
- 废弃
ReadToEnd()逻辑,改为持续逐字符/逐行读取标准输出流,直到再次识别到命令执行完成后返回的udec>提示符,即可判定本次计算完成,无需等待进程退出。 - 提示符判断逻辑增加前缀匹配,避免日志中其他
>字符触发误判。 - 计算完成后主动向标准输入写入程序退出指令,关闭UDEC进程避免后台残留。
修复后可运行代码:
using System; using System.Diagnostics; using System.Text; try { var psi = new ProcessStartInfo() { UseShellExecute = false, FileName = @"D:\Program Files\ITASCA\UDEC700\Exe64\udecConsole2017.exe", RedirectStandardInput = true, RedirectStandardOutput = true, CreateNoWindow = true, // 中文系统如果出现乱码可替换为Encoding.GetEncoding("GB2312") StandardOutputEncoding = Encoding.UTF8, StandardInputEncoding = Encoding.UTF8 }; var p = Process.Start(psi); StringBuilder outputCache = new StringBuilder(); // 读取启动日志,等待首次出现udec>提示符 while (true) { int readChar = p.StandardOutput.Read(); if (readChar == -1) break; // 进程意外退出直接跳出 char c = (char)readChar; Console.Write(c); outputCache.Append(c); // 匹配提示符,避免单个>字符误判 if (outputCache.ToString().TrimEnd().EndsWith("udec>")) { break; } } outputCache.Clear(); // 写入计算执行命令 string execCommand = @"call 'D:\Work\202205\20220525\tunnel-for-cmd.txt'"; p.StandardInput.WriteLine(execCommand); p.StandardInput.Flush(); // 持续读取计算日志,等待命令执行完成后再次出现udec>提示符 string calcResult = string.Empty; while (true) { int readChar = p.StandardOutput.Read(); if (readChar == -1) break; char c = (char)readChar; Console.Write(c); outputCache.Append(c); string currentOutput = outputCache.ToString(); if (currentOutput.TrimEnd().EndsWith("udec>")) { // 截取纯计算结果,剔除末尾提示符 calcResult = currentOutput.Replace("udec>", "").Trim(); break; } } // 写入退出命令关闭目标进程 p.StandardInput.WriteLine("quit"); p.StandardInput.Flush(); p.WaitForExit(); p.Close(); // 按需使用calcResult即可 Console.WriteLine("计算完成,结果如下:"); Console.WriteLine(calcResult); } catch (Exception e) { Console.WriteLine(e.Message); }
优化提示:如果逐字符读取性能不满足要求,可替换为
ReadLine()逐行读取,每次读取一行后判断行内容是否包含udec>提示符即可,逻辑完全一致,读取效率更高。
内容的提问来源于stack exchange,提问作者Lake_Lagunita
相关产品推荐
相关产品推荐

