异步获取可执行文件命令行日志失败:StringBuilder始终为空
背景
我写了一段异步运行可执行文件的C#代码,该可执行文件通常通过命令行运行,具体代码如下:
namespace Feather { internal class Helper { private static StringBuilder output; private static Process cmd; public static void RunLogic(string args) { output = new StringBuilder(); cmd = new Process(); try { cmd.StartInfo.FileName = "Cotton.exe"; cmd.StartInfo.Arguments = args; cmd.StartInfo.UseShellExecute = false; cmd.StartInfo.CreateNoWindow = true; cmd.StartInfo.RedirectStandardOutput = true; cmd.StartInfo.RedirectStandardInput = true; cmd.EnableRaisingEvents = true; cmd.OutputDataReceived += new DataReceivedEventHandler(cmd_OutputDataReceived); cmd.Exited += new EventHandler(cmd_Exited); cmd.Start(); } catch (Exception ex) { RhinoApp.WriteLine("Error on process start: {0}", ex.Message); } } private static void cmd_Exited(object sender, EventArgs e) { try { if (!string.IsNullOrEmpty(output.ToString())) { RhinoApp.WriteLine("Process output: {0}", output.ToString()); } RhinoApp.WriteLine("Process finished."); cmd.Dispose(); } catch (Exception ex) { RhinoApp.WriteLine("Error on process exit: {0}", ex.Message); } } private static void cmd_OutputDataReceived(object sender, DataReceivedEventArgs e) { try { RhinoApp.WriteLine("cmd_OutputDataReceived is called."); if (!String.IsNullOrEmpty(e.Data)) { output.Append(e.Data + Environment.NewLine); } } catch (Exception ex) { RhinoApp.WriteLine("Error on process data-receive: {0}", ex.Message); } } } }
问题
代码里的private static StringBuilder output;始终为空,output.Append(e.Data + Environment.NewLine);没法往里面加内容,我找不到原因。请问怎么获取这个可执行文件的所有命令行日志?有没有简便的实现方式?
解决方案
1. 修复原代码的核心问题
你的代码漏了关键一步:启动异步读取标准输出的操作。在cmd.Start();之后必须调用cmd.BeginOutputReadLine();,否则OutputDataReceived事件永远不会触发,自然无法收集到输出内容。
另外,多数命令行程序会把错误信息输出到标准错误流(StandardError),而非标准输出流,你当前代码未捕获这部分内容,需要补充处理:
// 在StartInfo配置中添加 cmd.StartInfo.RedirectStandardError = true; // 绑定错误流事件 cmd.ErrorDataReceived += cmd_ErrorDataReceived; // 启动进程后,同时启动错误流的异步读取 cmd.Start(); cmd.BeginOutputReadLine(); cmd.BeginErrorReadLine();
新增错误流处理方法:
private static void cmd_ErrorDataReceived(object sender, DataReceivedEventArgs e) { try { if (!string.IsNullOrEmpty(e.Data)) { output.Append($"Error: {e.Data}{Environment.NewLine}"); RhinoApp.WriteLine($"Process error: {e.Data}"); } } catch (Exception ex) { RhinoApp.WriteLine("Error on process error-receive: {0}", ex.Message); } }
同时注意:静态成员output和cmd存在线程安全问题,多次调用RunLogic会导致数据覆盖或异常,建议改为非静态实现,每次调用创建独立实例。
2. 简便实现方式(异步/await模式)
如果不需要实时处理输出,现代C#的异步/await模式能大幅简化代码,同时完整捕获所有输出和错误信息:
using System.Diagnostics; using System.Text; namespace Feather { internal class Helper { public static async Task RunLogicAsync(string args) { var processStartInfo = new ProcessStartInfo { FileName = "Cotton.exe", Arguments = args, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; using var process = new Process { StartInfo = processStartInfo }; try { process.Start(); // 异步读取输出和错误流 var outputTask = process.StandardOutput.ReadToEndAsync(); var errorTask = process.StandardError.ReadToEndAsync(); await process.WaitForExitAsync(); string output = await outputTask; string error = await errorTask; if (!string.IsNullOrEmpty(output)) { RhinoApp.WriteLine("Process output:\n{0}", output); } if (!string.IsNullOrEmpty(error)) { RhinoApp.WriteLine("Process error:\n{0}", error); } RhinoApp.WriteLine("Process finished."); } catch (Exception ex) { RhinoApp.WriteLine("Error: {0}", ex.Message); } } } }
这种方式无需处理事件,代码简洁且避免了静态成员的线程安全问题。
内容的提问来源于stack exchange,提问作者Megidd
相关产品推荐
相关产品推荐

