Как читать для завершения процесса вывода асинхронно в С#?

У меня проблема с чтением вывода одного процесса асинхронно в С#. Я нашел некоторые другие подобные вопросы на этом сайте, но они действительно не помогают мне. Вот что я делаю:

  • Сделать новый процесс
  • Установить startinfo -FileName, Аргументы, CreateNoWindow (true), UseShellExecute (false), RedirectStandardOutput (true)
  • Добавить обработчик событий в OutputDataReceived;
  • Запустить процесс, BeginOutputReadLine, а затем WaitForExit().

Он отлично работает, но вывод запущенного процесса записывает некоторые проценты (%), которые я хочу получить, но я не могу, так как мой код читается по строкам, а проценты не отображаются.

Пример:

%0,%1...%100
Finished.

Мой вывод:

%0
Finished. 

Вот текущий код моей программы:

StringBuilder sBuilder = new StringBuilder();
static void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    sBuilder.AppendLine(e.Data);
}

static void CommandExecutor()
{
    Process process = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = /*path of the program*/,
            Arguments = /*arguments*/,
            CreateNoWindow = true,
            UseShellExecute = false,
            WindowStyle = ProcessWindowStyle.Hidden,
            RedirectStandardOutput = true
        }
    };

    process.OutputDataReceived += new DataReceivedEventHandler(proc_OutputDataReceived);

    process.Start();

    process.BeginOutputReadLine();

    process.WaitForExit();
}

Ответ 1

Кажется, что поток потока чтения асинхронно бит разбит - не все данные считываются до выхода процесса. Даже если вы вызываете Process.WaitForExit(), и даже если вы затем вызываете Process.Close() (или Dispose()), вы все равно можете получить много данных впоследствии. См. http://alabaxblog.info/2013/06/redirectstandardoutput-beginoutputreadline-pattern-broken/ для полной записи, но в основном это решение использовать синхронные методы. Чтобы избежать тупика, вы должны вызвать один из них в другом потоке:

using (var process = Process.Start(processInfo))
{
    // Read stderr synchronously (on another thread)

    string errorText = null;
    var stderrThread = new Thread(() => { errorText = process.StandardError.ReadToEnd(); });
    stderrThread.Start();

    // Read stdout synchronously (on this thread)

    while (true)
    {
        var line = process.StandardOutput.ReadLine();
        if (line == null)
            break;

        // ... Do something with the line here ...
    }

    process.WaitForExit();
    stderrThread.Join();

    // ... Here you can do something with errorText ...
}

Ответ 2

Process.WaitForExit() будет ждать завершения чтения асинхронного вывода/ошибки. К сожалению, это не относится к перегрузке Process.WaitForExit(timeout). Это то, что делает класс Process внутри:

//...

finally
{
    if (processWaitHandle != null)
    {
        processWaitHandle.Close();
    }
    if (this.output != null && milliseconds == -1)
    {
        this.output.WaitUtilEOF();
    }
    if (this.error != null && milliseconds == -1)
    {
        this.error.WaitUtilEOF();
    }
    this.ReleaseProcessHandle(safeProcessHandle);
}

... Таким образом, он будет ждать, пока асинхронные чтения будут прочитаны, если не будет тайм-аута! Чтобы исправить это, просто вызовите без параметров WaitForExit() после того, как WaitForExit (timeout) вернёт true:

//...

if (process.WaitForExit( 10 * 1000 ) && process.WaitForExit() )
{
 // Process'es OutputDataReceived / ErrorDataReceived callbacks will not be called again, EOF streams reached
}
else
{
   throw new Exception("timeout");
}

Подробнее читайте здесь: http://msdn.microsoft.com/en-us/library/ty0d8k56%28v=vs.110%29

Ответ 3

Есть несколько вещей, которые мешают ему... Возможно, консольное приложение использует "\ b" backspace, чтобы перезаписать процент, возможно, не сбрасывая поток stdout после каждой записи, а BeginOutputReadLine предположительно ждет конца строки, прежде чем давать вам данные.

Посмотрите, как вы справляетесь с процессом чтения. StandardOutput.BaseStream через BeginRead (этот код не является надлежащим асинхронным, и "\ b" s будут обрабатываться по-разному, если вы выполняете прогресс в форме):

        while (true)
        {
            byte[] buffer = new byte[256];
            var ar = myProcess.StandardOutput.BaseStream.BeginRead(buffer, 0, 256, null, null);
            ar.AsyncWaitHandle.WaitOne();
            var bytesRead = myProcess.StandardOutput.BaseStream.EndRead(ar);
            if (bytesRead > 0)
            {
                Console.Write(Encoding.ASCII.GetString(buffer, 0, bytesRead));
            }
            else
            {
                myProcess.WaitForExit();
                break;
            }
        }