Как асинхронно читать стандартный поток вывода и стандартный поток ошибок сразу

Я хочу прочитать вывод процесса в форме, как есть в консоли (стандартный вывод смешивается со стандартной ошибкой в ​​одном потоке). Есть ли способ, как это сделать?

Я думал об использовании

ProcessStartInfo.UseShellExecute = true;  

но затем я не могу асинхронно читать вывод. Если я установил

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

тогда я могу читать стандартный вывод (я могу сделать то же самое для стандартной ошибки), но я не знаю, как имитировать поведение консоли (смешение stdout и stderr).

Примечание. Я знаю, что Linux имеет функцию перенаправления стандартного потока ошибок в стандартный поток вывода, но мне не удалось выполнить его для .NET.

Мне нужно пропустить что-то очень легкое.

Спасибо!

Ответ 1

Я нашел ответ:

Выходные потоки буферизуются. Невозможно получить истинный последовательный порядок элементов, вставленных в потоки. На самом деле это мало смысла, поскольку оба потока могут быть написаны тоже при одном и том же время. Они независимы друг от друга. Поэтому лучшее, что вы можете do - получить последовательный вывод от каждого из них по мере их поступления.

Как правило, это не проблема, поскольку почти все консольные приложения используют стандартный вывод для вывода и сообщений об ошибках. Поток ошибок используется некоторыми приложениями, но сообщения, как правило, являются дубликатами ошибки, генерируемые в выходном потоке.

Источник: http://social.msdn.microsoft.com/Forums/uk/csharpgeneral/thread/192b6df7-9437-42cf-81c1-c125021735ba

Ответ 2

Ты имеешь в виду что-то подобное?

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. Этот метод позволяет назначенное событие обработчик для вывода потока и немедленно возвращается к вызывающему, который может выполнять другую работу, в то время как поток обработчик событий.

Операции синхронного чтения вводят зависимость между вызывающим абонентом чтение из потока StandardError и запись дочернего процесса в этот поток. Эти зависимости могут привести к условиям взаимоблокировки. Когда вызывающий абонент читает из перенаправленного потока дочернего процесса, это зависит от ребенка. Вызывающий абонент ждет операции чтения пока ребенок не напишет в поток или не закрывает поток. Когда дочерний процесс записывает достаточно данных для заполнения перенаправленного потока, это в зависимости от родителя. Детский процесс ждет следующей записи пока родитель не прочитает полный поток или не закрывает поток. Условие взаимоблокировки возникает, когда вызывающий и дочерний процесс ждет друг друга, чтобы завершить операцию, и ни одна из них не может продолжить. Вы можете избежать взаимоблокировок, оценивая зависимости между вызывающий и дочерний процесс.

То же самое относится к StandardOutput, поэтому вы просто читаете оба потока асинхронно.

Merging оба потока в один затрудняют обнаружение того, какой вывод является сообщением об ошибках, и какова информация о продукте.

Ответ 4

Пример такого же типа, за исключением того, что я собираю stdout и error в отдельные строки, используя для этого 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;
}