Hi all,
I have some code that has proven to be very useful to me, and perhaps you guys may find it useful as well. However, I have one very difficult problem to solve -- and I'm hoping one of you can solve it.
The following code is essentially an implementation of a TraceListener that automatically separates Trace.WriteLine's from multiple threads into their own tab on a form. It also creates these new tabs automatically whenever a new thread does a Trace.WriteLine() for the first time. The tab name is based on the Thread.Name -- all null or "" named thread traces go to a "Main" tab.
However, I have found one particular call to Trace.WriteLine that will lock up my form -- and the debugging tools in the IDE are not helpful enough for me to solve it -- or at least I may not know enough to use them. This problem, by the way, happens in both the 1.1 and 2.0 framework.
Anyway, to create this program for yourself, start up a new Windows Form project and drop one TabControl on the form, and map 2 of the events below (Shown and FormClosing). Set the TabControl's .Dock property to Fill, and then use the code below (please excuse me if the formatting doesn't come through -- I haven't posted code to these forums before):
If you comment out "Trace.WriteLine("Threads started");" from the snippet below, everything works great. If you have any questions or need helping figuring a piece out, let me know.
namespace TraceForm
{
internal delegate PageDisplay AddNewTabDelegate(string TabName);
internal delegate void AddNewTextDelegate(string NewItem);
public partial class MainForm : Form
{
private static Hashtable m_tabbers;
private ArrayList m_threads;
public MainForm()
{
InitializeComponent();
m_tabbers = new Hashtable();
m_threads = new ArrayList();
m_Adder = new AddNewTabDelegate(AddNewTab);
Trace.Listeners.Add(new Logger(new AddNewTextDelegate(AddNewText)));
}
// Map to the Shown event
private void MainForm_Shown(object sender, EventArgs e)
{
Trace.WriteLine("Main form activated. Starting threads...");
for (int i = 0; i < 5; i++)
{
ThreadStart aThreadStart = new ThreadStart(new ThreadRunner().RunThread);
Thread aThread = new Thread(aThreadStart);
aThread.Name = string.Format("Thread{0}", i + 1);
m_threads.Add(aThread);
aThread.Start();
}
// Why does this "lock up" the form
// VS debugger says that it is in a sleep, wait, or join.
Trace.WriteLine("Threads started");
}
// Map to the FormClosing event
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
Trace.Listeners.Remove("Logger");
foreach (Thread aThread in m_threads)
{
aThread.Abort();
}
}
private void AddNewText(string NewItem)
{
string TabName = System.Threading.Thread.CurrentThread.Name;
if (TabName == null || TabName == "")
{
TabName = "Main";
}
PageDisplay TabToAddTo;
lock (m_tabbers.SyncRoot)
{
TabToAddTo = m_tabbers[TabName] as PageDisplay;
if (TabToAddTo == null)
{
if (tabPages.InvokeRequired)
{
object[] args = { TabName };
TabToAddTo = tabPages.Invoke(m_Adder, args) as PageDisplay;
}
else
{
TabToAddTo = m_Adder(TabName);
}
m_tabbers.Add(TabName, TabToAddTo);
}
}
TabToAddTo.WriteLine(NewItem);
}
private AddNewTabDelegate m_Adder;
private PageDisplay AddNewTab(string TabName)
{
PageDisplay RetVal = new PageDisplay(TabName);
tabPages.TabPages.Add(RetVal);
return RetVal;
}
}
// This is an individual thread that "does work" and writes updates through Trace
internal class ThreadRunner
{
public ThreadRunner()
{
}
public void RunThread()
{
Trace.WriteLine(string.Format("{0} started", Thread.CurrentThread.Name));
try
{
int i = 0;
while (true)
{
Trace.Write(string.Format("Test cycle {0}", ++i));
Thread.Sleep(TimeSpan.FromSeconds(2));
Trace.WriteLine(" completed");
}
}
catch (ThreadAbortException)
{
Trace.WriteLine("Thread aborting...");
}
finally
{
Trace.WriteLine("Thread finished");
}
}
}
// This is the TraceListener implementation
internal class Logger : TraceListener
{
private Hashtable m_threaditems; // Used to match Writes with WriteLines
private AddNewTextDelegate m_Adder;
public Logger(AddNewTextDelegate Adder)
{
m_Adder = Adder;
m_threaditems = new Hashtable();
Name = "Logger"; // This is so the Listener can be removed at the end
}
public override void Write(string message)
{
// Just add the Write to the hashtable
int iCurrentThread = Thread.CurrentThread.GetHashCode();
lock (m_threaditems.SyncRoot)
{
if (m_threaditems.ContainsKey(iCurrentThread))
{
m_threaditems[iCurrentThread] += message;
}
else
{
m_threaditems.Add(iCurrentThread, message);
}
}
}
public override void WriteLine(string message)
{
// Check to see if there are any Writes to go with this WriteLine
int iCurrentThread = Thread.CurrentThread.GetHashCode();
lock (m_threaditems.SyncRoot)
{
if (m_threaditems.ContainsKey(iCurrentThread))
{
message = m_threaditems[iCurrentThread] + message;
m_threaditems.Remove(iCurrentThread);
}
}
m_Adder(message);
}
}
// A TabPage that contains a ListBox for displaying text
internal class PageDisplay : System.Windows.Forms.TabPage
{
private delegate void AddItemDelegate(string item);
private System.Windows.Forms.ListBox ListItems;
private AddItemDelegate m_Adder;
public PageDisplay(string TabName)
{
m_Adder = new AddItemDelegate(WriteLine);
ListItems = new System.Windows.Forms.ListBox();
this.SuspendLayout();
ListItems.Dock = System.Windows.Forms.DockStyle.Fill;
ListItems.IntegralHeight = false;
this.Text = TabName;
this.Controls.Add(ListItems);
this.ResumeLayout();
}
public void WriteLine(string item)
{
if (ListItems.InvokeRequired)
{
object[] args = { item };
ListItems.Invoke(m_Adder, args);
}
else
{
ListItems.Items.Add(item);
}
}
}
}

Trace.WriteLine locking up my form
PsilentDev
By the way, you will need the following 'using' statements (not really all of them, since some were added automatically, but they will be enough):
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;
Omicron
Hi all,
I have finally solved this problem. Some of you who might be writing your own TraceListener in the future should find this helpful. The gist of this problem is that this code creates a race condition inside the core of the framework, and the .NET 2.0 framework provides 2 ways of solving it:
One is to declare your TraceListener as a thread-safe listener, by doing the following:
- calling 'Trace.UseGlobalLock = false' when your app loads
- and overriding 'bool IsThreadSafe' in your TraceListener and returning true.
This lets the system allow multiple threads to call into your TraceListener simultaneously (of course, your TraceListener should be threadsafe).
The second solution (which I hope will solve the problem in the 1.1 framework as well) is to modify WriteLine() in my TraceListener to call m_Adder.BeginInvoke() so that it can exit immediately. Of course, then, in this case the m_Adder delegate needs a second parameter to include the Thread.Name so that the delegate can create the tab with the appropriate name.
If any of you have any questions, please let me know.
David