TreeViews and Multi-Threaded Madness!

Disclaimer: The following information is for educational purposes only. Don't use at the track.

I'm currently working on an app (in C#) that performs three, related tasks. Each of the tasks is performed by an object, and the app's main form has as member variables these three objects. The form also spawns three threads, one for each object, so the tasks can be performed simultaneously. The objects in turn have events, which are handled by the main form. These event handlers take care of, among other things, updating the controls on the form to display the status and progress of the various tasks. One of the controls is a TreeView.

I ran into an interesting predicament a little while back when setting these event handlers up: when fired, the events could call the functions within the form class to remove nodes from the TreeView, or alter the text of the nodes, but when they tried to Add nodes, I received the following exception:

"The action being performed on this control is being called from the wrong thread. You must marshal to the correct thread using Control.Invoke or Control.BeginInvoke to perform this action."

Okay, that's a good, descriptive message, that points me in the right direction. From what I understand, because the TreeView was created on the app's main thread, and not one of the three threads I'm using for my task objects, I have to marshal to the main thread to add a new node. I found it odd that I could remove and alter the text of nodes without problem, but maybe it has to do with allocating resources on another thread.

So here's the fix: I created a delegate with the signature of the function my form uses to add the nodes, like such:

protected delegate void AddNodeToTreeDelegate( TreeNode ptnParentNode, string psNewNodeText, string psNewNodeTag );

Then, in my event handlers, I use the form's InvokeRequired property (which I cannot thank whoever decided to add this property enough) to decide whether or not to call my function directly or via Invoke. I'm sure it'll always be invoked, but this is better programming practice. But what function do I assign to the new delegate instance required by Invoke()? Why, the actual function itself! That's right -- I'm delegating to the function that I'd normally be calling directly. Check it out (in the example below, tnNode is a TreeNode, and sNewNodeText and sNewNodeTag are strings):

if( this.InvokeRequired )
   tvwQueues.Invoke( new AddNodeToTreeDelegate( AddNodeToTree ),
      new object[]( tnNode, sNewNodeText, sNewNodeTag ) )
   AddNodeToTree( tnNode, sNewNodeText, sNewNodeTag );

It's a pretty easy workaround to what could've been a real headache.


Popular Posts

How To Mock Out Child Components In Unit Tests of Angular 2 Code

A Generic Method Using HttpClient to Make a Synchronous Get Request

The Cause and Solution for the "System.Runtime.Serialization.InvalidDataContractException: Type 'System.Threading.Tasks.Task`1[YourTypeHere]' cannot be serialized." Exception

A Red Herring When Using Moq to Mock Methods With Optional Parameters

Unit Testing with a Mock Entity Framework DbContext and Fake DbSets