Nikolay Dakov
Nikolay Dakov

Reputation: 149

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

Upvotes: 9

Views: 18472

Answers (4)

Julien
Julien

Reputation: 1192

What about using a StreamReader on process.StandardOutput, and the using the Read() method ? http://msdn.microsoft.com/fr-fr/library/system.io.streamreader.read(v=vs.80).aspx

Upvotes: 0

Sly
Sly

Reputation: 408

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

Upvotes: 12

Peter Wishart
Peter Wishart

Reputation: 12270

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

Upvotes: 1

EM0
EM0

Reputation: 6327

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 ...
}

Upvotes: 10

Related Questions