views:

309

answers:

4

I've a RichTextBox which I use as output from a custom TraceListener shown below: The trouble is this seems to deadlock hard if anyone is writing debug/trace info from other threads while a GUI dialog also is trying to write debug/trace info.

Anyone care to point at some obvious errors here ? :

class MyTraceListener : TraceListener
{
 private RichTextBox output;

 public MyTraceListener (RichTextBox output) {
  this.Name = "DebugTrace";
  this.output = output;
 }


 public override void Write(string message) {
  Action append = delegate() { output.AppendText(message); };
  if (output.InvokeRequired) {
   IAsyncResult result = output.BeginInvoke(append);
   output.EndInvoke(result);
  } else {
   append();
  }
 }

 public override void WriteLine(string message) {
  Action append = delegate() { output.AppendText(message + "\r\n"); };
  if (output.InvokeRequired) {
   IAsyncResult result = output.BeginInvoke(append);
   output.EndInvoke(result);
  } else {
   append();
  }

 }
}
A: 

Just a thought, try using Control.Invoke as opposed to BeginInvoke. IE:

class MyTraceListener : TraceListener
{
        private RichTextBox output;

        public MyTraceListener (RichTextBox output) {
                this.Name = "DebugTrace";
                this.output = output;
        }


        public override void Write(string message) {
                Action append = delegate() { output.AppendText(message); };
                if (output.InvokeRequired) {
                        output.Invoke(append);
                } else {
                        append();
                }
        }

        public override void WriteLine(string message) {
                Action append = delegate() { output.AppendText(message + "\r\n"); };
                if (output.InvokeRequired) {
                        output.Invoke(append);
                } else {
                        append();
                }

        }
}
BFree
A: 

What if you try using this approach?

public override void Write(string message) {
    if (this.output.InvokeRequired)
    {
        this.output.Invoke((MethodInvoker)delegate
        {
            this.output.AppendText(message);
        });
     }
     else
     {
         this.output.AppendText(message);
     }
}
Joe
+1  A: 

Here's what's likely happening:

  • Thread 1: Gets to EndInvoke(); waiting to execute the delegate in the GUI thread.
  • GUI thread: is blocking in System.Diagnostics.Trace.WriteLine somewhere (afaik the trace system employs locks to be thread safe.)
  • Thread 1: will block forever, since the GUI thread is blocked and can't finish executing the delegate.

Calling just Invoke will likely not solve the problem, since Invoke blocks until the delegate is done running.

Calling only BeginInvoke should solve the locking, BeginInvoke will just initiate an asynchronous call to the GUI thread, not waiting till it's finished thus leaving the trace system and unblock the GUI thread. (I'm not sure about the ramification of not calling EndInvoke though.

nos
I've written essentially the same class as you with the exception of only calling BeginInvoke, it never deadlocks.There does not seem to be any ramifications for this usage: AFAIK there's no requirement to call EndInvoke as long as you do not care what the result of the invoke is.
Oplopanax
Well, other discussions on SO indicates you should call EndInvoke. But the post here sounds right in what's happening and avoid blocking in EndInvoke does indeed avoid locking up.
A: 

I would advise you to use a simple producer/consumer queue here. Your response to the trace write should be to add the string to a queue, then in the form displaying the data you can use a System.Windows.Forms.Timer() to empty the queue and display the text. Otherwise you are going to encounter issues and/or delays in the running program.

csharptest.net