ProcessInfo和RedirectStandardOutput

32

我有一个应用程序,在命令行窗口中调用另一个进程,该进程具有输出到控制台窗口的更新统计信息。我认为这是一个相当简单的操作,但似乎无法使其正常工作。我是否漏掉了什么?

string assemblyLocation = Assembly.GetExecutingAssembly().Location;

Process process = new Process
{
    ProcessStart =
    {
        RedirectStandardOutput = true,
        UseShellExecute = false,
        WindowStyle = ProcessWindowStyle.Hidden,
        Arguments = arg,
        FileName = assemblyLocation.Substring(0, assemblyLocation.LastIndexOf("\\")) + "\\ffmpeg.exe",
        CreateNoWindow = true
    }
};

process.Start();

Console.WriteLine(process.StandardOutput.ReadToEnd());

process.WaitForExit();

理想情况下,我希望在该过程中输出发生变化时,或者读取器接收到数据时,可以获取其事件。

任何帮助都将是极好的,我感觉这是一个新手问题,但似乎缺少了什么。

6个回答

54
我以前也遇到过这个问题。有时候,你调用的进程输出到控制台的方式不兼容这种输出重定向。在这种情况下,我很幸运能够修改外部进程来解决这个问题。
你可以尝试在另一个输出到控制台的进程上运行代码,看看是否正常工作。目前看起来没什么问题。
编辑:
我找了一段用于解决此问题的代码块。这是在WPF应用程序中将进程输出重定向到窗口的示例。请注意事件绑定。由于这是WPF,我必须调用我的写数据方法。由于您不必担心阻止,所以您可以简单地将其替换为:
Console.WriteLine(e.Data);

希望这有所帮助!

    private static void LaunchProcess()
    {
        Process build = new Process();
        build.StartInfo.WorkingDirectory =  @"dir";
        build.StartInfo.Arguments = "";
        build.StartInfo.FileName = "my.exe";

        build.StartInfo.UseShellExecute = false;
        build.StartInfo.RedirectStandardOutput = true;
        build.StartInfo.RedirectStandardError = true;
        build.StartInfo.CreateNoWindow = true;
        build.ErrorDataReceived += build_ErrorDataReceived;
        build.OutputDataReceived += build_ErrorDataReceived;
        build.EnableRaisingEvents = true;
        build.Start();
        build.BeginOutputReadLine();
        build.BeginErrorReadLine();
        build.WaitForExit();
    }

    // write out info to the display window
    static void build_ErrorDataReceived(object sender, DataReceivedEventArgs e)
    {
        string strMessage = e.Data;
        if (richTextBox != null && !String.Empty(strMessage))
        {
            App.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Send, (ThreadStart)delegate()
            {
                Paragraph para = new Paragraph(new Run(strMessage));
                para.Margin = new Thickness(0);
                para.Background = brushErrorBrush;
                box.Document.Blocks.Add(para);
            });
       }
    } 

你是指另一个线程还是另一个进程?我可以从命令行运行该进程,输出看起来很好。 - Brandon Grossutti
我指的是与您尝试的不同进程。在我遇到的情况下,当我重定向stdout时,该进程在以这种方式启动时不会清除其输出缓冲区,因此流最终会全部传输。这可能是您的问题,也可能不是。请参阅我的代码示例,了解我如何在自己的应用程序中处理此问题。 - patjbs
我可以让大多数控制台应用程序正常工作,但无法在PowerShell中运行。有什么想法吗? - 3Dave
2
@David Lively,你可以尝试使用未记录的PowerShell参数-InputFormat none。 - Peter Stephens
@PeterStephens 这很新鲜 - 我一定会尝试一下。 - 3Dave
@patjbs 这段代码片段非常有用。 - Rajaraman Subramanian

25

我不确定你遇到的具体问题是什么,但如果你想在输出生成后立即对其进行操作,请尝试挂接到进程的OutputDataReceived事件。你可以指定处理程序来异步地从进程接收输出。我已经成功地使用了这种方法。

Process p = new Process();
ProcessStartInfo info = p.info;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;

p.OutputDataReceived += p_OutputDataReceived;
p.ErrorDataReceived += p_ErrorDataReceived;

p.Start();

p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();

..

void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
  Console.WriteLine("Received from standard out: " + e.Data);
}

void p_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
  Console.WriteLine("Received from standard error: " + e.Data);
}

查看Process的OutputDataReceived事件以获取更多信息。


在执行 WaitForExit() 前,您需要先调用 Start() 方法启动进程。 - abatishchev
抱歉!我的粗心大意。我搞混了 Process p = Process.Start(info); - abatishchev
非常好的解决方案!这也解决了我遇到的一个问题,即太多的控制台输出导致生成的进程在Console.WriteLine处挂起。谢谢! - shindigo
@MichaelPetrotta非常感谢您!!这个解决方案非常好用,完美解决了我的问题:D - Ibrahim Amer
1
通常情况下,您应在开始处理之前附加事件处理程序。否则可能会错过事件。 - BatteryBackupUnit

13

使用 lambda 表达式,等等:

var info = new ProcessStartInfo(path)
{
    RedirectStandardError = true,
    RedirectStandardOutput = true,
    UseShellExecute = false,
    Verb = "runas",
};

var process = new Process
{
    EnableRaisingEvents = true,
    StartInfo = info
};

Action<object, DataReceivedEventArgs> actionWrite = (sender, e) =>
{
    Console.WriteLine(e.Data);
};

process.ErrorDataReceived += (sender, e) => actionWrite(sender, e);
process.OutputDataReceived += (sender, e) => actionWrite(sender, e);

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

关于此事需要注意的是:如果UseShellExecute = true,Verb =“runas”,则动词“runas”才能起作用。如果UseShellExecute = true,则无法重定向输出。更多信息请参见http://kiquenet.wordpress.com/2014/08/22/uac-run-as-administrator-elevated-process/。 - Kiquenet
这是一个很好的细节分析,涉及到捕获输出的一些微妙之处。感谢您的整理。 - Alexander Trauzzi

4

有趣的是,你不能同时从标准输出和标准错误读取:

如果你重定向了标准输出和标准错误,并尝试读取两者,例如使用以下C#代码:

[C#]

string output = p.StandardOutput.ReadToEnd();

string error = p.StandardError.ReadToEnd();

p.WaitForExit();

在这种情况下,如果子进程向标准错误写入任何文本,它将阻塞该进程,因为父进程在从标准输出读取完之前无法从标准错误读取。但是,父进程在进程结束之前不会从标准输出中读取。解决此问题的推荐方法是创建两个线程,以便应用程序可以在单独的线程上读取每个流的输出。

http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandardoutput(v=vs.71).aspx


2
并不是说没有办法同时读取两个流,只是在同一时间内仅使用同步方法读取两个流是不安全的。我只是想澄清@jeremy-mcgee所说的“不正确”并不意味着答案中引用的信息是错误的。 - Aardvark

1

流畅的代码在VS2010中运行正常。

void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        if (String.IsNullOrEmpty(e.Data) == false)
        {
            new Thread(() =>
            {
                this.Dispatcher.Invoke(new Action(() =>
                {
                    // Add you code here
                }));
            }).Start();
        }
    }

什么是Dispatcher?什么是this?什么是OnOutputDataReceived事件?什么是DataReceivedEventArgs类型?你的源代码的完整上下文是什么? - Kiquenet

0

检查您期望的输出是否被发送到标准输出而不是标准错误输出


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