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

问题描述 投票:0回答:5

我想以控制台中的形式读取进程的输出(标准输出与一个流中的标准错误混合)。有什么办法可以做到吗?

我正在考虑使用

ProcessStartInfo.UseShellExecute = true;  

但是我无法异步读取输出。如果我设置

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

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

这类似于Linux具有将标准错误流重定向到标准输出流的功能;怎么办?

c# .net process
5个回答
55
投票

你的意思是这样的吗?

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

void StartProcess()
{
    using (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);
}

3
投票

MSDN 文章指出:

重定向的StandardError流可以同步读取或者 异步地。 Read、ReadLine 和 ReadToEnd 等方法执行 对进程的错误输出流进行同步读取操作。 这些同步读取操作直到关联的 进程写入其 StandardError 流,或关闭该流。

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

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

这同样适用于

StandardOutput
,因此您只需异步读取两个流。

Merging
两者都流入一个,这使得检测什么输出是错误报告以及什么是“产品”信息变得复杂。


3
投票

我找到了答案:

输出流被缓冲。没有办法得到真实的 插入流中的项目的顺序。 事实上它 没有什么意义,因为两个流也可以同时写入 时间。 它们是相互独立的。 因此尽你所能 要做的就是在每个到达时获取它们的顺序输出。

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

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


2
投票

类似的示例,但我为此目的使用 StringBuilder 将标准输出和错误收集到单独的字符串中。

/// <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;
}

0
投票
Thread outputReaderThread = new(() => 
{
    StreamReader outputReader = currentShell.AdbShellProcess.StandardOutput;
    StreamReader errorReader = currentShell.AdbShellProcess.StandardError;

    while (!outputReader.EndOfStream || !errorReader.EndOfStream)
    {
        if (!errorReader.EndOfStream)
        {
            string? errorLine = errorReader.ReadLine();
        }

        if (!outputReader.EndOfStream)
        {
            string? outputLine = outputReader.ReadLine();
        }
    }
});
outputReaderThread.IsBackground = true;
outputReaderThread.Start();

Вот как можно читать синхронно оба потока в той послежовательности как они приходят без гонки потоков!

© www.soinside.com 2019 - 2024. All rights reserved.