Tracing from background threads

In my last post I introduced a TextBoxBaseTraceListener class that looked like this.

public class TextBoxBaseTraceListener : System.Diagnostics.TraceListener
{
public TextBoxBaseTraceListener(
System.Windows.Controls.Primitives.TextBoxBase control)
{
if (control == null)
{
throw new ArgumentNullException("control");
}
this.control = control;
}

  private System.Windows.Controls.Primitives.TextBoxBase control;

  public override void Write(string message)
{
control.AppendText(message);
}

  public override void WriteLine(string message)
{
control.AppendText(message);
control.AppendText(Environment.NewLine);
}
}

Let's play a bit with this, by creating a new project with the following window.

<Window x:Class="BackgroundSample.Window1"
xmlns="
https://schemas.microsoft.com/winfx/2006/xaml/presentation "
xmlns:x="
https://schemas.microsoft.com/winfx/2006/xaml "
Title="Background Sample" Height="300" Width="300"
>
<DockPanel>
<Button Name="RunButton" Click="RunButtonClick">Run</Button>
<RichTextBox Name="LogBox" IsReadOnly="True" />
</DockPanel>
</Window>

In the Window1.xaml.cs file, we can add the listener class, and the following bit of code.

public Window1()
{
InitializeComponent();
System.Diagnostics.Trace.Listeners.Add(
new TextBoxBaseTraceListener(LogBox));
}

public void RunButtonClick(object sender, EventArgs e)
{
new System.Threading.Thread(DoLongHardWork).Start();
}

private void DoLongHardWork()
{
System.Diagnostics.Trace.WriteLine("Doing long hard work...");
for (int i = 1; i <= 10; i++)
{
System.Threading.Thread.Sleep(500);
System.Diagnostics.Trace.Write(".");
}
System.Diagnostics.Trace.WriteLine("Done!");
}

As you can see, the long, hard-working DoLongHardWork method is pretty stand-alone - it's quite agnostic of what's going on. However, if you try running this sample and click the Run button, you will get an InvalidOperationException in TextBoxBaseTraceListener.WriteLine.

The problem is that the tracing is being invoked from the background thread, and it's trying to update the UI from a different thread. This is a no-no - regular WPF objects have thread affinity. To fix our code, we need to introduce a callback and change the Write and WriteLine methods a bit.

delegate void WriteCallback(string message);

public override void Write(string message)
{
if (control.Dispatcher.Thread != System.Threading.Thread.CurrentThread)
{
control.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal,
new WriteCallback(Write), message);
}
else
{
control.AppendText(message);
}
}

public override void WriteLine(string message)
{
if (control.Dispatcher.Thread != System.Threading.Thread.CurrentThread)
{
control.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal,
new WriteCallback(WriteLine), message);
}
else
{
control.AppendText(message);
control.AppendText(Environment.NewLine);
}
}

Now when we get called from a thread from which we cannot update the logging box, we'll ask the control dispatcher to queue us up for execution. Now try running this and clicking on the button again, and enjoy the wonderful display of dots!

Finally, if you want something more sophisticated for background thread management, you owe it to yourself to read the BackgroundWorker documentation.

This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at https://www.microsoft.com/info/cpyright.htm.