0

I've got a class, "TestRunner", with a callback for outputting results:

public class TestRunner
{
    // ...
    public delegate void OutputCallback(string msg);
    public OutputCallback Output { get; set; }
    // ...
}

In a form, I have a method that writes to a textbox that I set the callback equal to:

void WriteToOutput(string msg)
{
    // Version 1
    this.textBoxOutput.AppendText(msg);
}

public Form1()
{
    // ...
    installChecker = new TestRunner();
    installChecker.Output = WriteToOutput;
    // ...
}

When I run in the debugger, I get an InvalidOperationException because textBoxOutput is being accessed from a thread other than the thread it was created on, as discussed on MSDN and SO.

So I changed the WriteToOutput method as recommended in the above references:

delegate void WriteToOutputCallBack(string msg);
void WriteToOutput(string msg)
{
    // Version 2
    if (this.textBoxOutput.InvokeRequired)
    {
        WriteToOutputCallBack d = new WriteToOutputCallBack(WriteToOutput);
        this.Invoke(d, new object[] { msg });
    }
    else
    {
        this.textBoxOutput.AppendText(msg);
    } 
}

But this seems to have broken my program. In TestRunner, there's a method that redirects standard output from a process to the Output callback:

Process dir = new Process();
dir.StartInfo.RedirectStandardOutput = true;
dir.OutputDataReceived += (sender, args) => Output(args.Data + Environment.NewLine);
// ...

This appeared to work before I added Invoke to WriteToOutput, but with WriteToOutput Version 2, the program hangs when it handles the OutputDataReceived event.

Did I implement the UI cross-thread fix incorrectly? Any idea why it is hanging?

Community
  • 1
  • 1
MattDG
  • 373
  • 3
  • 14
  • How often is `this.Invoke` invoked? It could be that UI thread gets flooded with `this.Invoke` calls. If that's the case then the fix is to put data into some kind of buffer and output it once per some period of time, or producer-consumer pattern. – alex.b Nov 13 '15 at 21:26
  • what is the rest of your app doing? Is it just sitting waiting for user input or is it off doing something? – pm100 Nov 13 '15 at 21:31
  • @alex.b: When I debug with a breakpoint at this.Invoke, it hangs the first time it's called, so I don't think it's getting flooded with this.Invoke calls. – MattDG Nov 13 '15 at 22:00
  • @pm100: The form initializes a TestRunner, then calls a TestRunner method that executes a bunch of methods, returning results to the Output callback along the way. – MattDG Nov 13 '15 at 22:04
  • @MattDG: Try to change `this.Invoke(...)` to `this.textBoxOutput.Invoke(...)` in version 2. Please see [here](https://msdn.microsoft.com/en-us/library/a1hetckb(v=vs.110).aspx) more details about `Invoke()` method. – Didgeridoo Nov 13 '15 at 22:10
  • It doesn't "hang", the UI thread is just burning 100% core trying to keep up. Something you can see with Task Manager. When it gets in that state, caused by your code relentlessly slamming it with invoke requests, it doesn't get around doing its normal duties. Like painting and responding to input. That crazily scrolling textbox you'd get when it can keep up does not do your user a favor either, nobody can read that fast. Unusable UI produces unusable UI. – Hans Passant Nov 13 '15 at 22:56
  • @Didgeridoo: I tried this.textBoxOutput.Invoke(...) but got the same behavior. – MattDG Nov 14 '15 at 03:52
  • @HansPassant: I don't see a noticeable change in CPU usage in the Task Manager when the program has this problem. TestRunner is only asking it to print ~10 lines; it's not the flurry of scrolling textBoxes you describe. – MattDG Nov 14 '15 at 03:55
  • Then it is deadlock, you'll have to show us a stack trace. – Hans Passant Nov 14 '15 at 03:58
  • You are blocking the UI thread, which prevents the call to `Invoke()` from completing, deadlocking the program. A common suggestion to "fix" the problem is to switch to `BeginInvoke()`, but all this does is hide the problem; the deadlock doesn't occur, but the UI thread is still blocked (and so won't process the invoked delegate...it just gets queued up). The right fix is to **not block the UI thread**. If you have trouble figuring out how to do that, post a new question with [a good, _minimal_, _complete_ code example](http://stackoverflow.com/help/mcve) that reliably reproduces the problem. – Peter Duniho Nov 14 '15 at 07:24
  • @PeterDuniho, Hans: yes, it turns out I'm blocking the UI thread with a call to dir.WaitForExit() later in TestRunner. And yes, if I substitute BeginInvoke for Invoke it just queues the output calls and they end up in jumbled order in the output. I'm looking into a fix that executes TestRunner in a different thread from the beginning. – MattDG Nov 15 '15 at 18:33
  • I fixed this issue by making sure that TestRunner ran in a separate thread from the UI, using Task.Factory.StartNew. – MattDG Nov 15 '15 at 19:17

0 Answers0