Action Delegate and Task Parallelism in C#

I wrote a WPF program recently that contained 2 tree views which were dynamically created, in part, by data located on a database. Initially, I had the creation of the tree views in my Windows_Loaded method, executing the build one after the other. The performance was ok, it took about 1.8 to 2 seconds for the window to render and be ready for user input. I reviewed the code and thought to myself that I did not need to wait for one tree view to be built before I start, or complete, the other tree view. I could hear the code begging me to make these 2 lines run in parallel. So I did and now it takes less than .5 of a second for the window to render. Seeing it open now is like “instant on”.

At first when I started coding it, I broke the UI thread rule. This rule states that UI elements can only be manipulated by the thread it was created on. My first reaction to this error was to try and strip apart the methods. Meaning that I looked at the methods and thought about taking activities out which manipulated the UI, putting them into another method that I can call from the UI Thread. Luckily, it worked out good and I got a heavy method that could be called from a parallel thread and didn’t try to modify anything on the UI.

My original code was this:

[sourcecode language="csharp"]
TreeView1.ItemsSource = TreeViewModel.SetTree(true, true);
TreeView2.ItemsSource = TreeViewModel.SetTree(true, false);
[/sourcecode]

Like I mentioned already, doing the above in procedurally took about 2 seconds.

I modified my code to use an Action delegate and the Task class found in the Task Parallel Library. By doing this the 2 second activity was reduced to < .5 of a second.

[sourcecode language="csharp"]
TaskScheduler uiThread = 
    TaskScheduler.FromCurrentSynchronizationContext();
     
Action BackgroundThread = new Action(() =>
{
  treeViewClass1 = TreeViewModel.SetTree(true, true);
  treeViewClass2 = TreeViewModel.SetTree(true, false);
});
 
Action UiThread = new Action(() =>
{
  TreeView1.ItemsSource = treeViewClass1;
  TreeView2.ItemsSource = treeViewClass2;
});
 
var Task1 = Task.Factory.StartNew(() => BackgroundThread());
var Task2 = Task1.ContinueWith(t => UiThread(), uiThread);
[/sourcecode]

I first got the current UI thread context, so that I could use it later on in my code. I would need this to make sure when I execute a method that manipulates a UI element, that I use the correct thread.

Then I create my 2 Action delegates. The first one can run on a background thread and the second one I need to run on the UI thread because this is where I load the ItemsSource of the tree view control.

Lastly, I create my first Task, where I use a Lambda Expression to define the delegate and finally use the instance of the first task to call the ContinueWIth method. The ContinueWith method accepts a parameter for the thread to use, we pass it the UI thread we created at the start.

I was actually an amazing experience to see the performance improve so much. I had to run it a few times and stop it in Debug mode because it just seemed to run too fast. I thought it couldn’t possibly be doing everything it was before so fast. I was wrong, it’s fast, it rocks and I will implement this often from now on.

NOTE: When you begin the design of a project keep in mind what you have learned here. Like object-oriented design, you need to think about designing your methods in a way that allows for parallelism. I am sure there are some methods that simply can not be multi-threaded because they do non-UI work and UI work within the same unit. If you want you system to be object-oriented, start with a good object-oriented design. If you want to be able to run your method in parallel, design them with this capability.




Leave a Comment

Your email address will not be published.