在 Winforms 中操作进程输出 (C#)

Manupilating Process Output in Win Forms (C#)

我有一个 WinForms 应用程序,它 运行 多个进程 运行 作为后台工作程序。我为每个新进程创建一个后台工作者

BackgroundWorker background = new BackgroundWorker();
background.DoWork += new DoWorkEventHandler(bkWorker_DoWork);
background.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bkWorker_Complete);
background.WorkerReportsProgress = false;
background.WorkerSupportsCancellation = true;

我的 Dowork 代码如下所示:

private void bkWorker_DoWork(object sender, DoWorkEventArgs e) {
  WorkerArgument obj = (WorkerArgument) e.Argument;
  BackgroundWorker worker = (BackgroundWorker) sender;

  Process proc = new Process();
  proc.StartInfo.FileName = "cmdprocess"; //"cmd.exe";
  proc.StartInfo.UseShellExecute = false;
  proc.StartInfo.CreateNoWindow = true;
  proc.StartInfo.RedirectStandardOutput = true;
  proc.StartInfo.RedirectStandardInput = true;
  proc.StartInfo.RedirectStandardError = true;

  proc.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);

  proc.ErrorDataReceived += new DataReceivedEventHandler(ErrorOutputHandler);

  proc.Start();

  process.Add(obj.Row, proc.Id);
  proc.BeginOutputReadLine();
  proc.BeginErrorReadLine();
  proc.WaitForExit();
  proc.Close();
}

您可能会注意到,我正在使用事件处理程序对 cmdProcess (SortOutputHandler) 给出的输出进行排序,与错误输出相同。

我在文本框中显示此输出

 private void SortOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) {
   if (!String.IsNullOrEmpty(outLine.Data)) {
     if (txtLog.InvokeRequired) {
       this.Invoke((MethodInvoker) delegate {
         string[] lines = txtLog.Lines;
         if (lines.Length > 200) {
           string[] newlines = lines.Skip(200) as string[];
           txtLog.Lines = newlines;
         }
         txtLog.AppendText(Environment.NewLine + outLine.Data);
       });
     } else {
       string[] lines = txtLog.Lines;
       if (lines.Length > 200) {
         string[] newlines = lines.Skip(200) as string[];
         txtLog.Lines = newlines;
       }
       txtLog.AppendText(Environment.NewLine + outLine.Data);
     }


   }
 }

错误处理程序如下:

private void ErrorOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) {
  if (!String.IsNullOrEmpty(outLine.Data)) {
    if (txtLog.InvokeRequired) {
      this.Invoke((MethodInvoker) delegate {
        string[] lines = txtLog.Lines;
        if (lines.Length > 200) {
          string[] newlines = lines.Skip(200) as string[];
          txtLog.Lines = newlines;
        }
        txtLog.AppendText(Environment.NewLine + outLine.Data);

      });
    } else {
      string[] lines = txtLog.Lines;
      if (lines.Length > 200) {
        string[] newlines = lines.Skip(200) as string[];
        txtLog.Lines = newlines;
      }
      txtLog.AppendText(Environment.NewLine + outLine.Data);
    }

  }
}

问题是,当进程数增加时,日志会迅速启动 运行ning。

我创建了一个新的表单 frmLog,里面有一个多行文本框,你能告诉我如何使用它从一个进程中获取输出吗?就像可以有一个 "View Log" 按钮,当我单击它时只显示该进程的日志。

是否可以将所有进程的输出分别写入唯一的文件?

首先,请考虑以下类型来保存进程输出的示例

interface IProcessOutput
{
  IReadOnlyCollection<string> Cerr { get; }
  IReadOnlyCollection<string> Cout { get; }
  void OnError(string text);
  void OnOutput(string text);
}

然后,启动一个新进程,实例化一个实现该接口的类型和"bind"事件到相应的对象。 如果您想滚动日志(使用 2000 或任何限制),我鼓励您使用 Queue(如 IReadOnlyCollection),因为 Queue 在内部保存头部和尾部,因此如果您保持一定的容量限制,它不会 allocate/move 内存。它足够快了。

更改 GUI,使带有日志输出的 ListBox 可以属于任何 运行 个进程(类似于 master/detail)。所以,一旦你 select 一个进程,你就会看到相应的日志行。

请使用 ListBox 而不是文本框。在将字符串加载到其中时使用 BeginUpdate() 和 EndUpdate()。如果您的输出量很大,请在列表框中使用自绘制,这样您就不需要将字符串存储到它的 Items 集合中,而是直接访问队列对象。

PS:准备好接收不构成完整行的字符串(没有换行符)。当进程刷新文件句柄(cerr、cout 也是文件句柄)时,会在内部调用带输出的事件。因此,如果进程在字符串中间刷新它 - 您将获得字符串的一半。