Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read to end process output asynchronously in C#?

I have problem with reading the output of one Process asynchronously in C#. I found some other similar questions on this site but they don't really help me. Here is what I do:

  1. Make new process
  2. Set startinfo -FileName, Arguments, CreateNoWindow(true), UseShellExecute(false), RedirectStandardOutput(true)
  3. Add event handler to OutputDataReceived;
  4. Start process, BeginOutputReadLine and then WaitForExit().

It works fine but the output of the started process writes some percents(%) which I want to get but I can't since my code reads line by line and the percents don't show up.

Example:

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

My output:

%0
Finished. 

Here is the current code of my program:

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();
}
like image 819
Nikolay Dakov Avatar asked Mar 02 '12 12:03

Nikolay Dakov


3 Answers

Process.WaitForExit() will wait until the asynchronous output / error stream reading finished. Unfortunately this is not true for Process.WaitForExit(timeout) overload. This is what the Process class does internally:

//...

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

... So it will wait for the async reads only if there was no timeout! To fix it simply call the parameterless WaitForExit() after WaitForExit(timeout) returned 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");
}

For details read the remarks here: http://msdn.microsoft.com/en-us/library/ty0d8k56%28v=vs.110%29

like image 57
Sly Avatar answered Sep 18 '22 13:09

Sly


It seems that reading stream output asynchronously is a bit broken - not all the data is read before the process exits. Even if you call Process.WaitForExit() and even if you then call Process.Close() (or Dispose()) you can still get a lot of data afterwards. See http://alabaxblog.info/2013/06/redirectstandardoutput-beginoutputreadline-pattern-broken/ for a full write-up, but the solution is basically to use synchronous methods. To avoid a deadlock, though, you have to call one of them on another thread:

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 ...
}
like image 29
EM0 Avatar answered Sep 22 '22 13:09

EM0


There are few things that are getting in the way of it...

The console app is probably using "\b" backspace to overwrite the percentage, its maybe not flushing to the stdout stream after every write, and the BeginOutputReadLine presumably waits for the end of line before giving you data.

See how you get on with reading process.StandardOutput.BaseStream via BeginRead (this code isn't proper async and the "\b"s will need processed differently if your putting progress in a form):

        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;
            }
        }
like image 34
Peter Wishart Avatar answered Sep 19 '22 13:09

Peter Wishart