2

I am using .NET and C# to start a process and read it's output asynchronously. My problem is that there seems to be a delay before the output is read by my program. If I run the executable on the command line, there is output immediately when it starts running. But when I run it using my code the ReadOutput event handler isn't called until the Process exits. I want to use this to provide a real-time view of the process's output, so I don't want to wait (several minutes) until the process exits.

Here's some relevant code:

MyProcess = new Process();
MyProcess.StartInfo.FileName = command;
MyProcess.StartInfo.Arguments = args;
MyProcess.StartInfo.UseShellExecute = false;
MyProcess.StartInfo.RedirectStandardOutput = true;
MyProcess.StartInfo.RedirectStandardError = true;
MyProcess.StartInfo.RedirectStandardInput = true;
MyProcess.OutputDataReceived += new DataReceivedEventHandler(ReadOutput);
MyProcess.ErrorDataReceived += new DataReceivedEventHandler(ReadOutput);

if (!MyProcess.Start())
{
    throw new Exception("Process could not be started");
}

try
{
    MyProcess.BeginOutputReadLine();
    MyProcess.BeginErrorReadLine();
}
catch (Exception ex)
{
    throw new Exception("Unable to begin asynchronous reading from process";
}

And here's my event handler:

private void ReadOutput(object sendingProcess, DataReceivedEventArgs outLine)
{
    OutputBuilder.AppendLine(outLine.Data);
    Console.WriteLine(outLine.Data);
    Console.Out.Flush();
}
Brian
  • 1,191
  • 2
  • 13
  • 26
  • 2
    You're using the same method to handle asynchronous events for both OutputData and ErrorData. I would not design it this way. Especially since the `OutputBuilder` member (a `StringBuilder`, I assume?) is being unsafely accessed from both threads. I would split this up into two methods and have each method log to a separate `StringBuilder` instance. – Jesse C. Slicer Mar 03 '11 at 22:59
  • none of the answers here seems so solve the issue. I am having the exact same problem where the process is long gone, but the streams are still being written to somehow. – OSH Aug 13 '14 at 14:23

3 Answers3

3

This is the way I do it (C# 3) as per my comment using the lambda syntax.

    /// <summary>
    /// Collects standard output text from the launched program.
    /// </summary>
    private static readonly StringBuilder outputText = new StringBuilder();

    /// <summary>
    /// Collects standard error text from the launched program.
    /// </summary>
    private static readonly StringBuilder errorText = new StringBuilder();

    /// <summary>
    /// The program's entry point.
    /// </summary>
    /// <param name="args">The command-line arguments.</param>
    /// <returns>The exit code.</returns>
    private static int Main(string[] args)
    {
        using (var process = Process.Start(new ProcessStartInfo(
            "program.exe",
            args)
            {
                CreateNoWindow = true,
                ErrorDialog = false,
                RedirectStandardError = true,
                RedirectStandardOutput = true,
                UseShellExecute = false
            }))
        {
            process.OutputDataReceived += (sendingProcess, outLine) =>
                outputText.AppendLine(outLine.Data);

            process.ErrorDataReceived += (sendingProcess, errorLine) =>
                errorText.AppendLine(errorLine.Data);

            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
            process.WaitForExit();
            Console.WriteLine(errorText.ToString());
            Console.WriteLine(outputText.ToString());
            return process.ExitCode;
        }
Jesse C. Slicer
  • 19,457
  • 3
  • 66
  • 82
1

The problem in your approach is probably that the process only finishes the output of a line when it exits. There's no way to control when the asynchronous event handlers fire.

In a console application, your best bet is to periodically check for new output and read and display it synchronously:

        while (!p.HasExited)
        {
            if (!p.StandardOutput.EndOfStream)
            {
                errorBuilder.Append(p.StandardError.ReadToEnd());
                outputBuilder.Append(p.StandardOutput.ReadToEnd());
                Console.Write(p.StandardOutput);
            }
            else
            {
                Thread.Sleep(200);
            }
        }

In a UI project, you'd use Timer or DispatcherTimer for WinForms and WPF, respectively, to call the contents of the loop and update the UI.

Note that I don't flush Console.Out, as Console.Write() and Console.WriteLine() cause this automatically.

Tamschi
  • 1,095
  • 7
  • 22
  • Update: I added a delay to the loop. I somewhere read that 200 milliseconds is the average time it takes to register an event consciously, so this should be a good compromise between low CPU use and a fluent output. – Tamschi Mar 04 '11 at 15:23
  • 1
    It appears that `StandardOutput.EndOfStream` sometimes hangs until the stream closes. I tried the suggested change from @Tamschi and my program started to hang. When I attached to the process with the debugger, it showed that it was hanging on the `EndOfStream` property. Another post ([link](http://stackoverflow.com/questions/2767496/standardoutput-endofstream-hangs)) indicates that this property may hang, but I don't understand why. Does anyone have an idea of why this would hang or have another idea? – Brian Mar 14 '11 at 20:31
  • 2
    The MSDN documentation explains that the the StandardOutput code above could result in a deadlock where the process waits indefinitely for StandardOutput stream to close. https://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput(v=vs.110).aspx – John Zabroski Sep 28 '16 at 14:25
0

Try to add MyProcess.WaitForExit method call:

MyProcess.BeginOutputReadLine();
MyProcess.BeginErrorReadLine();

// will wait for the associated process to exit
MyProcess.WaitForExit();
Oleks
  • 31,334
  • 11
  • 76
  • 131