如何一次异步读取标准输出流和标准错误流

时间:2012-09-24 13:34:16

标签: c# .net process

我想在控制台中读取表单中的进程输出(标准输出与一个流中的标准错误混合)。有办法怎么做?

我在考虑使用

ProcessStartInfo.UseShellExecute = true;  

然后我无法异步读取输出。如果我设置

process.ProcessStartInfo.UseShellExecute = false;  
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += new DataReceivedEventHandler(partialOutputHandler);

然后我可以读取标准输出(我可以对标准错误做同样的事情)但我不知道如何模拟控制台的行为(混合stdout和stderr)。

注意:我知道Linux具有将标准错误流重定向到标准输出流的功能,但我无法将其谷歌用于.NET。

我必须错过一些非常简单的事情。

谢谢!

4 个答案:

答案 0 :(得分:39)

你的意思是这样吗?

SynchronizationContext _syncContext;
MyForm()
{
    _syncContext = SynchronizationContext.Current;
}

void StartProcess()
{
    var process = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = "myProcess.exe",
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
        }
    };

    process.OutputDataReceived += (sender, args) => Display(args.Data);
    process.ErrorDataReceived += (sender, args) => Display(args.Data);

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

    process.WaitForExit(); //you need this in order to flush the output buffer
}

void Display(string output)
{
    _syncContext.Post(_ => myTextBox.AppendText(output), null);
}

答案 1 :(得分:3)

我找到了答案:

  

输出流被缓冲。没有办法得到真实的   插入到流中的项的顺序。实际上它   没有意义,因为两个流也可以同时写入   时间。它们彼此独立。因此,你可以做到最好   do是在每个到达时得到每个的顺序输出。

     

通常这不是问题,因为几乎所有的控制台应用程序都使用   输出和错误消息的标准输出。错误流   某些应用程序使用它,但消息通常是重复的   输出流中生成的错误。

来源: http://social.msdn.microsoft.com/Forums/uk/csharpgeneral/thread/192b6df7-9437-42cf-81c1-c125021735ba

答案 2 :(得分:2)

MSDN article声明:

  

可以同步读取重定向的StandardError流   异步。执行Read,ReadLine和ReadToEnd等方法   对进程的错误输出流进行同步读取操作。   这些同步读取操作在关联之前不会完成   进程写入其StandardError流,或关闭流。

     

相反,BeginErrorReadLine启动异步读取操作   StandardError流。此方法启用指定事件   流输出的处理程序并立即返回给调用者,   它可以在流输出定向到时执行其他工作   事件处理程序。

     

同步读取操作在调用者之间引入依赖关系   读取StandardError流和子进程写入   那条小溪。这些依赖项可能导致死锁条件。   当调用者从子进程的重定向流中读取时,   它取决于孩子。调用者等待读操作   直到孩子写入流或关闭流。当。。。的时候   子进程写入足够的数据来填充其重定向流,它是   依赖于父母。子进程在下一次写入时等待   操作直到父进程从完整流中读取或关闭   流。当调用者和孩子出现死锁情况时   进程等待彼此完成一个操作,也都不能   继续。您可以通过评估之间的依赖关系来避免死锁   来电者和孩子的过程。

同样适用于StandardOutput,因此您只需异步读取两个流。

Merging两个流合并为一个变得复杂,检测错误报告的输出和“产品”信息是什么。

答案 3 :(得分:0)

类似的示例,除了我为此目的使用StringBuilder将stdout和错误收集到单独的字符串中。

/// <summary>
/// Executes command
/// </summary>
/// <param name="cmd">command to be executed</param>
/// <param name="output">output which application produced</param>
/// <param name="transferEnvVars">true - if retain PATH environment variable from executed command</param>
/// <returns>true if process exited with code 0</returns>
static bool ExecCmd(string cmd, out String output, bool transferEnvVars = false)
{
    ProcessStartInfo processInfo;
    Process process;

    if (transferEnvVars)
        cmd =  cmd + " && echo --VARS-- && set";

    processInfo = new ProcessStartInfo("cmd.exe", "/c " + cmd);
    processInfo.CreateNoWindow = true;
    processInfo.UseShellExecute = false;
    processInfo.RedirectStandardError = true;
    processInfo.RedirectStandardOutput = true;

    process = Process.Start(processInfo);

    // Executing long lasting operation in batch file will hang the process, as it will wait standard output / error pipes to be processed.
    // We process these pipes here asynchronously.
    StringBuilder so = new StringBuilder();
    process.OutputDataReceived += (sender, args) => { so.AppendLine(args.Data); };
    StringBuilder se = new StringBuilder();
    process.ErrorDataReceived += (sender, args) => { se.AppendLine(args.Data); };

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

    output = so.ToString();
    String error = se.ToString();

    if (transferEnvVars)
    {
        Regex r = new Regex("--VARS--(.*)", RegexOptions.Singleline);
        var m = r.Match(output);
        if (m.Success)
        {
            output = r.Replace(output, "");

            foreach ( Match m2 in new Regex("(.*?)=([^\r]*)", RegexOptions.Multiline).Matches(m.Groups[1].ToString()) )
            {
                String key = m2.Groups[1].Value;
                String value = m2.Groups[2].Value;
                Environment.SetEnvironmentVariable(key, value);
            }
        }
    }

    if(error.Length != 0)
        output += error;
    int exitCode = process.ExitCode;

    if (exitCode != 0)
        Console.WriteLine("Error: " + output + "\r\n" + error);

    process.Close();
    return exitCode == 0;
}