You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Process.StandardInput未关闭StreamWriter写入时死锁挂起问题

C#与常驻C++进程标准IO双向交互死锁问题

问题背景

对接C++实现的常驻状态机进程时遇到标准输入输出挂起/死锁问题:

  • C++进程标准输入逻辑完全符合规范,常驻运行,可通过标准输入接收指令、存储变量、解析表达式并返回计算结果,要求单次输出后仍可持续接收后续输入,不能关闭输入流、不能重启进程
  • 公开资料给出的通用解决方案是写入完成后调用Process.StandardInput.Close(),该方案确实能解决挂起问题,但关闭StandardInput对应的StreamWriter后后续交互无法进行,完全不适用于本场景
  • 测试用C++进程逻辑为循环读取标准输入行并原样回显,代码如下:
int main () {
   std::string input;
   while (getline(std::cin, input)) {
      std::cout << input << std::endl;
   }
}

初始实现问题

最初的C#封装类逻辑为启动重定向标准输入输出的进程,开启StandardInput.AutoFlush,Run方法写入输入后直接调用ReadToEnd读取输出。该代码仅当取消p.StandardInput.Close()注释时可正常运行——原因是ReadToEnd会持续读取直到流末尾,只有关闭输入流触发C++进程退出、输出流关闭时才会返回,关闭流后后续Run调用会直接失效。
初始实现代码:

public class Expression {

   System.Diagnostics.Process p;

   public Expression () {
      p = new System.Diagnostics.Process();
      p.StartInfo.UseShellExecute = false;
      p.StartInfo.RedirectStandardOutput = true;
      p.StartInfo.RedirectStandardInput = true;
      p.StartInfo.FileName = "InputEcho.exe";
      p.Start();
      p.StandardInput.AutoFlush = true;
   }

   public void Run (in string input, out string output) {
       p.StandardInput.WriteLine(input);
      // p.StandardInput.Close();
      output = p.StandardOutput.ReadToEnd();
   }
}

故障现象:主程序连续两次传入字符串测试,预期依次打印Hello、Hi,实际仅打印启动提示后程序就挂起无响应。

方案选型踩坑

后续尝试过基于事件委托的异步输出接收方案,但该方案难以适配同步收发的API封装需求:现有业务调用链路需要Run方法传入输入后直接返回对应输出,供上层逻辑同步判断结果,若采用全局容器存储异步接收的输出则存在竞态条件风险。

落地实现方案

最终实现了异步输出监听+输出队列+超时等待的封装方案:通过OutputDataReceived事件将接收的输出存入队列,Run方法仅负责写入输入,GetOutput方法阻塞等待队列中有数据时出队返回,同时加入超时机制避免无限等待。
代码实现如下:

public class Expression {

   System.Diagnostics.Process p = new();
   System.Collections.Generic.Queue<string> outputQ = new();

   public Expression () {
      p.StartInfo.UseShellExecute = false;
      p.StartInfo.RedirectStandardOutput = true;
      p.StartInfo.RedirectStandardInput = true;
      p.StartInfo.FileName = "C2E2.exe";
      p.OutputDataReceived += (s, e) => {
         outputQ.Enqueue(e.Data);
      };
      p.Start();
      p.BeginOutputReadLine();
   }

   /// 遇到错误返回自定义异常对象
   public GRLib.Exception Run (in string input) {

      if (p == null) return GRLib.Exception.New("Expression Evaluator not operational.");

      try {
         p.StandardInput.WriteLine(input);
      }
      catch (Exception e) {
         return GRLib.Exception.New(e.Message);
      }

      return null;
   }

   /// 超时返回错误码1,timeout单位为毫秒,传0则无限等待,阻塞调用
   public GRLib.Exception GetOutput (out string output, int timeout = 2000) {

      int timeWaited = 0;
      while (outputQ.Count == 0) {
         System.Threading.Thread.Sleep(100);
         if (timeout != 0 && (timeWaited += 100) > timeout) {
            output = "ERR";
            return GRLib.Exception.New(1, "Get timed out.");
         }
      }

      output = outputQ.Dequeue();

      return null;
   }

// 其余类实现省略
}

调用示例

Expression expression = new();

var e = expression.Run("3 > 2");
if (e != null) // 处理错误

string output;
e = expression.GetOutput(out output);
if (e != null) // 处理错误

// output即为返回结果"true",可直接供业务逻辑使用

待优化点

目前该方案可满足调用栈中输入输出同序列返回的需求,仍有两点待优化:

  • 当前等待循环采用Thread.Sleep(100)轮询实现,是否存在更优的等待循环实现方式
  • 是否存在更优雅的常驻进程同步交互实现方案

内容的提问来源于stack exchange,提问作者GhostRavenstorm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 10:45:33