7

I am having trouble redirecting console output to a windows forms text box. The problem is thread related. I am running a console app in the following way,

private void RunConsoleApp()
{
    Process proc = new Process();
    proc.StartInfo.FileName = "app.exe";
    proc.StartInfo.Arguments = "-a -b -c";
    proc.StartInfo.UseShellExecute = false;

    // set up output redirection
    proc.StartInfo.RedirectStandardOutput = true;
    proc.StartInfo.RedirectStandardError = true;    
    proc.EnableRaisingEvents = true;
    proc.StartInfo.CreateNoWindow = true;

    // Set the data received handlers
    proc.ErrorDataReceived += proc_DataReceived;
    proc.OutputDataReceived += proc_DataReceived;

    proc.Start();
    proc.BeginErrorReadLine();
    proc.BeginOutputReadLine();
    proc.WaitForExit();

    if (proc.ExitCode == 0)
    {
        out_txtbx.AppendText("Success." + Environment.NewLine);
    }
    else
    {
        out_txtbx.AppendText("Failed." + Environment.NewLine);
    }
}

and then capture and process the data with this output handler,

// Handle the date received by the console process
void proc_DataReceived(object sender, DataReceivedEventArgs e)
{
    if (e.Data != null)
    {
        if ((e.Data.EndsWith("DONE.")) || (e.Data.EndsWith("FAILED.")) ||
            (e.Data.StartsWith("RESET")))
        {
            // This crashes the application, but is supposedly the correct method
            this.AppendText(e.Data + Environment.NewLine);

            // This works, but the debugger keeps warning me that the call
            // is not thread safe
            //out_txtbx.AppendText(e.Data + Environment.NewLine);
        }
    }
}

The console text is then appended like this,

delegate void AppendTextDelegate(string text);

// Thread-safe method of appending text to the console box
private void AppendText(string text)
{
    // Use a delegate if called from a different thread,
    // else just append the text directly
    if (this.out_txtbx.InvokeRequired)
    {
        // Application crashes when this line is executed
        out_txtbx.Invoke(new AppendTextDelegate(this.AppendText), new object[] { text });
    }
    else
    {
        this.out_txtbx.AppendText(text);
    }
}

From all the documentation and examples I have seen this appears to be the correct method, except that it is crashing the application when out_txtbx.Invoke is called.

What could be broken and what alternative ways are there to do this?


Solution (as pointed out by Hans Passant)

The problem is that the app is stuck in a "deadly embrace" as a result of the line,

proc.WaitForExit();

That line should be removed and the method should look like this,

private void RunConsoleApp()
{
    Process proc = new Process();
    proc.StartInfo.FileName = "app.exe";
    proc.StartInfo.Arguments = "-a -b -c";
    proc.StartInfo.UseShellExecute = false;

    // set up output redirection
    proc.StartInfo.RedirectStandardOutput = true;
    proc.StartInfo.RedirectStandardError = true;    
    proc.EnableRaisingEvents = true;
    proc.StartInfo.CreateNoWindow = true;

    // Set the data received handlers
    proc.ErrorDataReceived += proc_DataReceived;
    proc.OutputDataReceived += proc_DataReceived;

    // Configure the process exited event
    proc.Exited += new EventHandler(ProcExited);

    proc.Start();
    proc.BeginErrorReadLine();
    proc.BeginOutputReadLine();

    // This blocks the main thread and results in "deadly embrace"
    // The Process.Exited event should be used to avoid this.
    //proc.WaitForExit();
}

and an event handler should be provided,

/// <summary>
/// Actions to take when console process completes
/// </summary>
private void ProcExited(object sender, System.EventArgs e)
{
    Process proc = (Process)sender;

    // Wait a short while to allow all console output to be processed and appended
    // before appending the success/fail message.
    Thread.Sleep(40);

    if (proc.ExitCode == 0)
    {
        this.AppendText("Success." + Environment.NewLine);
        ExitBootloader();
    }
    else
    {
        this.AppendText("Failed." + Environment.NewLine);
    }

    proc.Close();
}
default locale
  • 13,035
  • 13
  • 56
  • 62
Duncan Drennan
  • 871
  • 2
  • 9
  • 21
  • I have done this without error. If there is not an adequate answer by the time I can get to my code sample, I'll post. Note that this will be later this evening when I get home. I had created a TextWriter object that wrote to a textbox, then directed the Console.SetOut to the TextWriter. See http://msdn.microsoft.com/en-us/library/system.console.setout%28v=VS.90%29.aspx – IAbstract Dec 15 '10 at 18:47
  • @decyclone: He says it's on the line where out_txtbx.Invoke is called. –  Dec 15 '10 at 18:50
  • @SLaks No error, the app just hangs when I either run it, or try to step into/over the out_txtbx.Invoke line. Visual C# does not appear to provide a pause when debugging (only pause on breakpoints), so I have no idea what happens beyond that call. – Duncan Drennan Dec 15 '10 at 19:04
  • Is the main Winforms thread busy doing something else? – cdhowie Dec 15 '10 at 19:12
  • Click the Pause button on the Debug toolbar, or press Ctrl+Pause in VS. – SLaks Dec 15 '10 at 19:13

2 Answers2

10
proc.WaitForExit();

It is called deadlock. Your main thread is blocked, waiting for the process to exit. That stops it from taking care of essential duties. Like keeping the UI updated. And making sure that Control.Invoke() requests are dispatched. That stops the AppendText() method from completing. Which stops the process for exiting. Which stops your UI thread from ever getting past the WaitForExit() call. "Deadly embrace", aka deadlock.

You cannot block your main thread. Use the Process.Exited event instead.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
0

try

out_txtbx.Invoke(new AppendTextDelegate(this.AppendText), text);
RocketMan
  • 4,289
  • 2
  • 26
  • 19