实时控制台输出重定向使用进程

16

我正在使用VBOXMANAGE来“导出”一台客户机。 VBOXManage是一个控制从主机上的客户机行为的控制台应用程序。由于导出命令是一个长时间运行的过程,因此它会返回类似以下的进度更新:

0%...10%...20%...30%...100%

我正在编写一个C#应用程序来使用Process调用VBOXManage。这是我的代码:

Process VBOXProc = new Process();

VBOXProc.StartInfo.FileName = VBOXMANAGE;
VBOXProc.StartInfo.Arguments = Arguments;
VBOXProc.StartInfo.UseShellExecute = false;
VBOXProc.StartInfo.CreateNoWindow = true;
VBOXProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
VBOXProc.StartInfo.RedirectStandardError = true;
VBOXProc.StartInfo.RedirectStandardOutput = true;

VBOXProc.OutputDataReceived += new DataReceivedEventHandler(VBOXProc_OutputDataReceived);
VBOXProc.ErrorDataReceived += new DataReceivedEventHandler(VBOXProc_ErrorDataReceived);

VBOXProc.EnableRaisingEvents = true;

VBOXProc.Start();
VBOXProc.BeginOutputReadLine();
VBOXProc.BeginErrorReadLine();

VBOXProc.WaitForExit();

这很好,但输出是按行读取的,这意味着进程更新“0%... 10%... 20%... 30%... 100%”仅会在实际进程完成后显示。

有没有一种方法可以实时捕获控制台输出?

谢谢!


请注意函数名称:BeginOutput ReadLine - Hans Passant
是的,谢谢nobugz提供的精彩见解。 ;) - Ian
几个 With 语句会让那段代码更易读(也更方便复制)... With VBOXProc ... With .StartInfo ... End With ... End With. - Basic
4个回答

10
这对我有用:

这对我有用:

process.StartInfo.CreateNoWindow = true;
process.StartInfo.ErrorDialog = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;

process.ErrorDataReceived += (sendingProcess, errorLine) => error.AppendLine(errorLine.Data);
process.OutputDataReceived += (sendingProcess, dataLine) => SetMessage(dataLine.Data);

process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();

process.WaitForExit();

error.AppendLine()SetMessage() 是我使用的方法。


只有在输出流被终止时才能正常工作。但是在我的例子中,输出流在单行上,并且只有当进程完成时才会终止。 - Ian

7

您可以使用所有标准流方法直接从进程的StandardOutput/Error中读取,只需确保将StartInfo.Redirectxxx设置为true。

var p = new Process()
p.StartInfo.UseShellExecute = false;  //not sure if this is absolutely required
p.StartInfo.RedirectStandardOuput = true;
....

do
{
  Thread.Sleep(nnn);
  Console.Out.Write(p.StandardOutput.ReadToEnd());
}
while (!p.HasExited);
//catch any leftovers in redirected stdout
Console.Out.Write(p.StandardOutput.ReadToEnd());

以上代码将子进程的输出回显到您的应用程序标准输出。

您可以使用p.StandardOutput.Read(char[], int, int)读取特定大小的块,也可以使用p.StadardOutput.BaseStream.BeginRead(...)进行异步读取。

所有相同的方法都适用于StandardError。

在循环中睡眠可释放处理器以完成其他任务,并允许某些数据在缓冲区中累积。如果睡眠时间过长且缓冲区溢出,则执行过程中的某些输出将会丢失。如果睡眠时间太短,则会花费大量CPU周期来读取和清空缓冲区。


嗨,上面的代码有些不可靠...我得到了被截断的文本。 - Ian
是的,根据向标准输出写入的时间和进程终止的时间,循环终止后缓冲区中可能会有一些数据残留。在循环之后进行另一次读取将捕获任何丢失的数据。我主要想指出所有流方法都是可用的。在生产代码中,我可能会在循环中休眠或以其他方式释放处理器,并且只偶尔读取。 - ScottS
1
ReadToEnd() 在进程运行时会阻塞。 - Herman

1
尝试重定向标准输入并对StandardInput应用AutoFlush。然后使用StreamReader读取流。
Process proces;
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "test.exe";
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = true;

proces = Process.Start(psi);
proces.StandardInput.AutoFlush = true;

1

抱歉有任何错误,我是巴西人并且使用谷歌翻译写这段文字。

巧合的是,我也正在做一个与Virtualbox的VBoxManage相关的程序。在我的情况下,我想要将虚拟磁盘转换为其他格式,并显示进度百分比。

通过创建一个即将运行程序的进程并使用用户类 'Dean North'(与问题类似),我成功地实现了它。使用线程来运行VBoxManage非常重要,否则无法处理获取的文本或查看进度。

这段文字太长了,我无法在每一行前添加四个空格再转发。

这些类替代了系统类 Process,不需要对您的代码进行任何更改,只需添加一个包含用户Dean North传递的文本的arquivo.cs文件,而不是使用Process p = new Process(),请使用FixedProcess p = new FixedProcess()

之后就是我的代码:

private void BotaoParaTestes_Click(object sender, EventArgs e)
{
    string linha = @"clonehd " +
        "\"Z:\\Máquinas Virtuais\\Teste.vdi\" " +
        "\"C:\\Temp\\teste.vdi\" " +
        "--format VDI --variant Standard";

    Thread tarefa = new Thread(Executar);
    tarefa.Start(linha);
}
private void Executar(object Linha)
{
    FixedProcess fp = new FixedProcess ();
    fp.StartInfo.FileName = ItensEstaticos.VBox;
    fp.StartInfo.Arguments = Linha.ToString();
    fp.StartInfo.CreateNoWindow = true;
    fp.StartInfo.ErrorDialog = false;
    fp.StartInfo.RedirectStandardError = true;
    fp.StartInfo.RedirectStandardOutput = true;
    fp.StartInfo.UseShellExecute = false;
    fp.ErrorDataReceived += (sendingProcess, errorLine) => Escrita(errorLine.Data);
    fp.OutputDataReceived += (sendingProcess, dataLine) => Escrita(dataLine.Data);
    fp.Start();
    fp.BeginErrorReadLine();
    fp.BeginOutputReadLine();

    fp.WaitForExit();
}
private void Escrita(string Texto)
{
    if (!string.IsNullOrEmpty(Texto))
    {
        BeginInvoke(new MethodInvoker(delegate
        {
            this.Texto.Text += Texto;
        }));
    }
}

对我来说,只有在文本更改时才会触发事件,而不仅仅是当VBoxManage换行时。有时文本为空,那么需要像之前使用控件获取的文本一样进行检查结构处理。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接