Threading Support in the .NET Framework

Threading Support in the .NET Framework

he .NET Framework introduces advanced free threading as a fundamental building block. The Framework offers solutions even for advanced issues, enabling developers to build truly thread-safe applications in a relatively simple fashion. This makes multi-threading available to all .NET developers, no matter what language they choose to implement their solutions.

What Is Multi-Threading?
In simple terms, multi-threading is the ability to run different pieces of code (methods) seemingly at the same time. One can imagine multi-threading like multi-tasking within a single application.

Of course, computers generally cannot do multiple things at the same time (unless you’re talking about a multi-processor system). Instead, different tasks and threads have to share processing resources and the operating system assigns “time slices” to different tasks and threads in a round-robin fashion.

You can use multi-threading for a variety of scenarios. A simple example is Internet Explorer: Whenever one uses IE to navigate to a Web site, the download engine is busy retrieving information from the web site. However, while information downloads, the user interface is “alive” and ready to be used. The user can move the window around, resize it, and if parts of the page have already been downloaded they can scroll up and down in the part of the page that has already been rendered. The reason one can use the interface while downloading and rendering is still in progress is that all these things run on separate threads. Presumably there is a thread for the interface, a thread for downloading information, and a rendering thread (and perhaps others that are not as obvious). If all these things were done on the same thread, the user would have to wait for a page to download completely and then render before they could browse to a different site or even close the window.

Another multi-threading example is the background spell checker that serves me quite well while I am typing text into MS Word. As I type, Word silently checks my spelling and highlights spelling mistakes as I go. This feature does not interrupt my typing no matter how quickly I type. Spell checking happens on a separate thread (probably on a background thread?a thread of lower priority?ensuring that it will not take away too much processing time from the main application thread).

Most readers are not about to implement their own Web browsers or text processing applications, but how about an auto-complete textbox that finds matching values in a database while the user types? (You can see similar behavior in Internet Explorer when you enter a URL in the address bar.) You wouldn’t want to interrupt the user while they’re typing, so a task like this is best performed using a separate thread.

Or how about background verification of data entry forms? Instead of verifying entries when the user tabs out of a textbox (or other control), which is very interruptive if the logic is complex and perhaps data intensive, this could be done on a background task using a lower priority thread.

Implementing a Simple Example
So how do you go about creating your own threads? The basic idea is to create a Thread object based on the Thread class that you’ll find in the System.Threading namespace. Consider the following VB.NET example:

  Imports System.Threading   Public Class SlowClass      Public Sub DoSomething()         Me.DoSomethingSlow()         MsgBox("Done!")      End Sub      Public Sub DoSomethingSlow()         ' Imagine some slow code here...         Thread.Sleep(10000)      End Sub   End Class

This code simulates a slow operation. It doesn’t do anything with multi-threading despite the inclusion of the System.Threading namespace. I simply use Thread.Sleep() to put the main application thread to sleep for 10 seconds, which simulates a slow process.

When the DoSomething() method is called it will immediately branch out to the DoSomethingSlow() method. This method will take 10 seconds to execute before it returns control to the first method, which will ultimately display a message box.

If you instantiate this object and call it from a Windows form, the form will be non-responsive for 10 seconds. The form cannot be moved, resized, or closed. In fact, if you switch to a different task and then back to your form, the form will even fail to repaint itself.

Now let’s look at the same example in a multi-threaded fashion:

  Imports System.Threading   Public Class SlowClass      Public Sub DoSomething()         Dim oThread As New _         Thread(AddressOf Me.DoSomethingSlow)         oThread.Start()         MsgBox("Done!")      End Sub      Public Sub DoSomethingSlow()         ' Imagine some slow code here...         Thread.Sleep(10000)      End Sub   End Class

Here I’ve made a fairly simple change?I create a new Thread object and point it to the address of the DoSomethingSlow() method, which I then pass to the object’s constructor as I create it. This provides the thread information about what I’d like it to execute. Now all that’s left is to start the thread that will execute the method on a separate thread.

When you run this example you’ll notice that it displays the message box instantaneously because the DoSomething() method doesn’t wait for the DoSomethingSlow() method to complete. Instead, the DoSomethingSlow() method runs on its own thread entirely independent from the main thread. This allows the first thread to continue with the next line of code right away.

Note that both threads run truly independent from each other. Not only does the message box not have to wait for the slow method to complete, but the slow method is equally independent from the first method and proceeds on its 10 second quest no matter how long the message box stays up. You can easily verify this by adding another message box to the slow method:

  Public Sub DoSomethingSlow()      ' Imagine some slow code here...      Thread.Sleep(10000)      MsgBox("Slow Method Done!")   End Sub

Of course, you can write the same example in C# as well:

  using System.Threading;   public class SlowClass   {      public void DoSomething()      {         ThreadStart oTS = new         ThreadStart(this.DoSomethingSlow);         Thread oThread = new Thread(oTS);         oThread.Start();         MessageBox.Show("Done!");      }      public void DoSomethingSlow()      {         // Imagine some slow code here         Thread.Sleep(10000);      }   }

Note that there is a slight difference between the C# and the VB.NET example! In C#, you cannot just point the Thread object at the address of a method. Instead, you have to generate a ThreadStart delegate (see sidebar “Delegates” for more information on this subject) that points to the slow method. You then proceed to pass that delegate to the Thread object and start it the same way you did in the VB.NET example.

See also  Comparing different methods of testing your Infrastructure-as-Code

Note that the two languages don’t really differ very much. VB.NET allows you to take a shortcut through the AddressOf operator. In fact, it would have been perfectly fine to use the ThreadStart delegate in VB.NET (and it provides more flexibility in many cases):

  Public Sub DoSomething()      Dim oTS As New ThreadStart( _      AddressOf Me.DoSomethingSlow)      Dim oThread As New Thread(oTS)      oThread.Start()      MsgBox("Done!")   End Sub

At this point VB.NET developers may point out that their language is more productive since the ThreadStart object is optional. C# enthusiasts on the other hand would promote the fact that no AddressOf operator is required in their language. I will remain neutral on the subject.

(See Sidebar: Pre-Emptive vs. Cooperative Thread Switching)

Integrating with Secondary Threads
At this point you have a nice multi-threaded system that doesn’t do much. The secondary thread runs for 10 seconds and goes away. Normally methods return some result you’re interested in. However, this wouldn’t make sense in this environment. Why would you spin up a new thread if you make the original thread wait for a result?

There are a number of ways to overcome this problem. One of them employs a technique called polling where the original thread goes on about its business until it absolutely needs the result created by the second thread. The second thread could put a return value into a property of the class and the first thread could simply run in a loop until the value has been set. The parents among you will intuitively know that this is not a very good approach, (“Are we there yet? Are we there yet? Are we there yet? No? How about now?”). We will not explore this approach further and instead investigate more elegant solutions.

A more elegant solution is to use what is known as a callback where you tell a thread to call a certain method whenever it is done with whatever it does. (You can also use callbacks to report on the progress of a method while the thread runs.)

In .NET you implement callbacks through the native Delegate mechanism (see sidebar). Delegates allow you to provide information about a certain method to another process so the process can dynamically call that method without having to know beforehand what method is to be called.

Let’s say you want your secondary thread to call the following method whenever it”s done (VB.NET):

  Public Sub ShowProgress(ByVal Text As String)   MsgBox(Text)   End Sub

In the first step towards passing this information to the second thread, create the associated Delegate:

  Delegate Sub ShowProgressDelegate( _   ByVal Text As String)

You can see that a Delegate is basically a method definition with the same parameters and return values as the actual methods.

Here’s the same construct in C#:

  public void ShowProgress(string Text)   {      MessageBox.Show(Text);   }   delegate void ShowProgressDelegate(string Text);

We’ll now instantiate this Delegate before we start the secondary thread, point it to the ShowProgress() method, and make it available for the secondary thread. The easiest way to do that is to add a new field to the class:

  Private oCallback As ShowProgressDelegate

We’ll change the DoSomething() method to create the Delegate before we start the secondary thread:

  Public Sub DoSomething()      Dim oThread As New Thread(AddressOf _         Me.DoSomethingSlow)      Me.oCallback = New ShowProgressDelegate( _         AddressOf Me.ShowProgress)      oThread.Start()      MsgBox("Done!")   End Sub

Now that the Delegate is available, the DoSomethingSlow() method can invoke it:

  Public Sub DoSomethingSlow()      Thread.Sleep(10000)      Me.oCallback("Done")   End Sub

This will call the ShowProgress() method, which will in turn show a message box. Here’s the C# version of the field as well as the DoSomething() method:

  private ShowProgressDelegate oCallback;   public void DoSomething()   {      ThreadStart oTS = new      ThreadStart(this.DoSomethingSlow);      Thread oThread = new Thread(oTS);      this.oCallback = new         ShowProgressDelegate(this.ShowProgress);      oThread.Start();      MessageBox.Show("Done!");   }

And this is how it gets called:

  public void DoSomethingSlow()   {      Thread.Sleep(10000);      this.oCallback("Done");   }

(See Sidebar: Fibers)Synchronization Issues
What if you could allow the secondary thread to simply influence objects used by the first thread? You can. For instance, you can build a user interface that spins up a secondary thread that retrieves a DataSet from SQL Server or a Web service and then populates a DataGrid object on a form. However, this introduces the issue of thread safety since the DataGrid and the form really belong to the first thread. Let’s investigate the issue of thread safety before we proceed.

The great advantage of a free-threaded model is that there can be multiple paths of program execution that run in parallel. All paths share the same resources. Among those resources are things such as variables, fields, properties, and objects. (In an Apartment Threading Model these resources are not shared. See sidebar for more information.) A problem arises when two or more threads, by sheer coincidence, try to access the same resources.

Let’s consider a very simple example. Assume we have an object that stores address information in a number of fields. Let’s also assume the object has methods to set and get that information. See Listing 1 and Listing 2 for examples in C# and VB.NET.

While these classes would work perfectly fine in single-threaded environments and they would even work fine most of the time in multi-threaded environments, they are not thread-safe. Let’s assume that two different threads call the GetAddress() and SetAddress() methods at roughly the same time. Let’s say the thread that called SetAddress() was slightly ahead of the GetAddress() method. So SetAddress() works through the first few lines of code and replaces the two address lines and the city information. Then the other thread kicks in and executed GetAddress(). Of course at this point in time the information stored in the object is bogus since only half the address has been updated by the first thread.

Scenarios like these lead to bugs that are very hard to find because they occur only sometimes, depending on plain coincidence. This very simple example would result in bogus output as the worst-case scenario. Many thread-safety issues end up more severe with application crashes or worse.

See also  Comparing different methods of testing your Infrastructure-as-Code

How can you fix the problem? In the current example the main problem is caused by the SetAddress() method. You would not be bothered at all by multiple threads calling the GetAddress() method at the same time. While the information is updated you really don’t want another thread to mess with your object. You can easily indicate this to the operating system using a so-called sync lock (in other environments this is known as a critical section). Both C# and VB.NET consider this issue important enough to make sync locks part of the core language. Here’s how you can implement sync locks. C# first:

  public bool SetAddress(string addressLine1,   string addressLine2, string city,   string zip, string state, string country)   {      lock (this)      {         this.AddressLine1 = addressLine1;         this.AddressLine2 = addressLine2;         this.City = city;         this.ZIP = zip;         this.State = state;         this.Country = country;         return true;      }   }

And the Visual Basic .NET version:

  Public Function SetAddress( _      ByVal addressLine1 As String, _      ByVal addressLine2 As String, _      ByVal city As String, ByVal zip As String, _      ByVal state As String, ByVal country As String)      SyncLock Me         Me.AddressLine1 = addressLine1         Me.AddressLine2 = addressLine2         Me.City = city         Me.ZIP = zip         Me.State = state         Me.Country = country      End SyncLock   End Function

Both versions behave identically. Using a sync lock block you indicate to the operating system that a process requires exclusive access to a certain resource (the current object?this or Me?in this case). All other threads that are trying to gain access to the resource are put into a wait state until the lock ends.

This mechanism solves our problem. However, there are other (different) synchronization issues. Beyond that, sync lock blocks introduce some additional issues, most noteworthy is the issue of deadlocks. Imagine that there are two different types of addresses you need to keep track of (such as a mailing and a billing address). Imagine that one thread locks the mailing address, and by sheer coincidence, a second thread locks the billing address at the same time. The first thread may then try to also lock the billing address. However, it can’t do so, until the second thread unlocks that resource. This is where things get critical. If the second thread in fact unlocks the billing address, everything will work out fine. However, if the second thread attempts to lock the mailing address, all hell breaks loose! It simply will never be able to do so, because that address is already locked by the first thread, and will not be unlocked until the first thread is done. The first thread, on the other hand, can’t complete before it can lock the second address. That isn’t going to happen until the second thread lets go of it, which would only be the case if the second thread could lock the other resource, which it can’t since that address is locked by the first thread.

At this point you have two hung threads. There is no technical solution to this logistical problem. The developer has to be very careful with these kinds of nested locks. This type of problem is very hard to debug because it will only occur randomly. The consequence, however, is rather severe.

At this point you might wonder what other problems may occur. While a long list of these types of problems is beyond the scope of this article, I would like to point out one other issue that isn’t immediately obvious. Here’s another simple VB.NET example:

  Public Class Racer      Private Counter As Integer = 0      Public Sub Execute()         Me.Counter = 0         Do While Me.Counter < 1000            Console.WriteLine( _               Me.Counter.ToString())            Me.Counter = Me.Counter + 1         Loop      End Sub   End Class

I designed this simple class to increase a field (Me.Counter) 1000 times by increments of 1. However, if two threads execute this method at the same time, each thread will attempt to increase the counter, but both threads will only loop 500 times (on average... results will vary) since they try to count up the same member at the same time, (an issue known as racing).

The solution to this problem is more of a design issue than it is an implementation issue. You could wrap the loop in a sync lock, but that would defeat the purpose of multi-threading, as the second thread would have to wait for the first one to complete, hence making the process sequential.

Our scenario is not just a logical problem, but a very technical problem as well. Once you've compiled the code a single line such as the Me.Counter = Me.Counter +1 will end up as multiple processing instructions. This means that a thread may be interrupted half way though the execution of a single line of code. Therefore, incrementing a potentially shared resource in the way described above is inherently non-thread-safe.

The same statement would not be true for local variables, which by definition are thread-safe. The following example won't result in any threading issues:

  Dim iCounter As Integer   iCounter = 0   Do While iCounter < 1000      Console.WriteLine( _         iCounter.ToString())      iCounter = iCounter + 1   Loop

But back to our problematic example! How could you increment the counter field in a thread-safe fashion? The easiest way to do so is through the Interlocked class:


Similarly, you could decrement a variable using the Decrement() method or even swap two variables through the Exchange() method. The field or variable you want to increment has to be passed to the Increment() method by reference, therefore the C# version would look as follows:

  Interlocked.Increment(ref this.Counter);

As mentioned above, you may encounter a number of other issues when you're trying to build multi-threaded applications. It is impossible to explore them all within this article. Please refer to the Visual Studio .NET Framework documentation for more information on the subject. There are also a number of books dedicated to the subject of threading.

Threading and Windows Forms
One of the issues that surprises most developers is that .NET Windows Forms are not thread-safe. It is not safe to call a method on a Windows form or control from a secondary thread. Although this may seem strange at first, as a developer you actually want this. Thread-safety doesn't come for free! If Windows Forms were programmed in a thread-safe way, all single threaded applications would pay a hefty performance penalty.

See also  Comparing different methods of testing your Infrastructure-as-Code

Does that mean Windows Forms applications cannot be used in a multi-threaded environment? No! It simply means that the architect of a multi-threaded Windows Forms application has to worry about thread-safety himself.

Let's look at an example. Consider the code in Listing 3?a very simple Windows Form with a DataGrid control. When the form loads, the LoadCustomers() method retrieves customer information from a Web service (I did not include the Web service in my example). Once you have the DataSet, you use the DisplayCustomers() method to display the customers in the DataGrid.

The problem with this example is that it may be slow to load large customer lists from a Web service, and you don't want the application to appear hung while the download is in progress. You know that you can easily fix this problem by spinning up a new worker thread for the LoadCustomers() method:

  private void Form1_Load(object sender,      System.EventArgs e)   {      ThreadStart oTS = new      ThreadStart(this.LoadCustomers);      Thread oThread = new Thread(oTS);      oThread.Start();   }

Here you're creating a separate thread for the LoadCustomers() method and starting the thread right away, which performs a background download of the data while the main form is already operational. Once LoadCustomers() completes it's work it calls DisplayCustomers() to update the grid. At least this sounds OK. However, when you try to run this application you'll generate the following exception on the line of code that attempts to set the DataSource of the DataGrid:

  "An unhandled exception of type    'System.ArgumentException' occurred in Additional information:    Controls created on one thread cannot be parented to a    control on a different thread."

You tried to interact with a Windows Forms object from a thread other than the one that created the control (DataGrid in this example). So how do you hand control back over to the first thread? You use the Windows Forms control Invoke() method. It allows execution of any method on the form's or control's main thread. Invoke() expects a Delegate (see sidebar) as well as a list of arguments (parameters). Since Invoke() has to be generic it cannot know the number of parameters to expect. Therefore, you'll pass the parameters as an array.

You'll now add another method called DisplayCustomer_TS() to the form class that allows you to update the grid in a thread-safe fashion. You'll use DisplayCustomers_TS() not to update the grid directly but to call the original DisplayCustomers() method in a thread-safe manner:

  private void DisplayCustomers_TS(   DataSet dsCustomers)   {      DisplayCustomersDelegate oDel = new         DisplayCustomersDelegate(         this.DisplayCustomers);      DataSet[] args = {dsCustomers};      this.Invoke(oDel,args);   }   delegate void DisplayCustomersDelegate(      DataSet dsCustomers);

The first line instantiates a delegate that points to your original DisplayCustomers() method. (Note that the last line in this snippet defines the delegate.) You then create an array of DataSet objects with a single item (your customer DataSet). Finally, you'll call the Invoke() method with the delegate and the DataSet in the array of arguments. If you aren't familiar with Delegates this may look a bit confusing at first but it is really quite straightforward. Simply point the Invoke() method at another method and say, "See that method? Go ahead and run it for me..."

That does the trick! The only thing that's left to do is make the LoadCustomers() call DisplayCustomers_TS() instead of DisplayCustomers(). Now your little application is perfectly thread-safe! Not surprisingly, the Visual Basic .NET implementation of this solution is rather similar:

  Private Sub DisplayCustomers_TS( _      ByVal dsCustomers As DataSet)      Dim oDel As New _      DisplayCustomersDelegate( _         AddressOf Me.DisplayCustomers)      Dim args() As DataSet = {dsCustomers}      Me.Invoke(oDel, args)   End Sub   Delegate Sub DisplayCustomersDelegate( _      ByVal dsCustomers As DataSet)

Thread Properties and Methods
So far, you've created new threads and started them. However, you can do more with threads. For instance, we can adjust a thread's priority:

  oThread.Priority = ThreadPriority.BelowNormal

.NET let's you fine-tune how much processing time each thread will get. There are a number of other things you can do with a thread such as give it a friendly name or query an internal thread ID for identification. You can also suspend, abort, or continue a thread, and much more. For more information on this subject, please refer to the .NET Framework SDK documentation.

Threads Everywhere
Now that you know threads are relatively easy to use in .NET, shouldn't you use threads everywhere to improve performance? No! Threads do not improve performance. They make a system more responsive by creating the illusion of performing multiple tasks at once rather than putting one process on hold while it performs another. All threads taken together have the same processing power as before (unless the application runs on a multi-processor machine, which can truly execute two things at the same time). The system must use some of the available processing time for the thread switching operations making total performance worse than before. Don't underestimate this overhead! In fact, if you spin up a lot of threads, switching between the threads may take up more time than running the threads. (See Sidebar: The Thread Pool)

Therefore, you want to use threads carefully. The Windows Forms example above demonstrated a very good use for a thread; you used a secondary thread for a process that could potentially be very slow and make your interface appear hung. I can imagine a scenario where it could take minutes to download a large customer list from a Web service. After 15-30 seconds (maybe even less than that), I would expect the user to get somewhat concerned about the state of the application if the main window entirely fails to refresh beyond showing the title bar. Having a non-responsive application not only annoys the user and could guide them toward terminating the entire process through CTRL-ALT-DEL, it also looks very unprofessional.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist