9

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();
}
Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
Nikolay Dakov
  • 149
  • 1
  • 2
  • 10
  • Have you put a breakpoint on the appenline method to see how many times it is hit? In the console app, do the percentages overwrite eachother or are the continuous? – John Koerner Mar 02 '12 at 12:17
  • The percentages overwrite. I will put a breakpoint to see what will happen... – Nikolay Dakov Mar 02 '12 at 12:25
  • 1
    It isn't called BeginOutputRead**Line** by accident. The program doesn't output any lines. And it normally doesn't output anything when output is redirected unless the program explicitly calls flush(). You can't fix this. – Hans Passant Mar 02 '12 at 13:06

4 Answers4

12

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

Yousha Aleayoub
  • 4,532
  • 4
  • 53
  • 64
Sly
  • 408
  • 4
  • 8
  • This is the proper way to fix this issue. Should be the official answer. – Erunehtar Mar 26 '18 at 18:14
  • 1
    This doesn't work if the process doesn't complete. For example if you want to isolate code in a process because it can enter a zombie state, this approach will stay on the WaitForExit() statement forever :-( – RikRak Jun 15 '20 at 23:50
10

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 ...
}
EM0
  • 5,369
  • 7
  • 51
  • 85
  • 3
    thanks, worked for me. Silly broken BeginOutputReadLine – Joshua Glazer Jul 09 '15 at 01:11
  • 2
    @EM0, the link seems broken. – Chau Jul 26 '17 at 14:18
  • Not the proper solution. Check @Sly answer. – Erunehtar Mar 26 '18 at 19:26
  • 1
    Sly's answer brings up a big "gotcha", for sure, but neither the question nor my answer uses the `WaitForExit(timeout)`, so I'm not sure why this is "not the proper solution" to the original question. Could you elaborate on what is wrong with it @Deathicon? – EM0 Feb 26 '21 at 10:31
1

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;
            }
        }
Yousha Aleayoub
  • 4,532
  • 4
  • 53
  • 64
Peter Wishart
  • 11,600
  • 1
  • 26
  • 45
0

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

Yousha Aleayoub
  • 4,532
  • 4
  • 53
  • 64
Julien
  • 1,181
  • 10
  • 31