Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Threading Support in the .NET Framework : Page 3

To provide a great user experience applications need to do a lot of thingsmany of them seemingly at the same time. Professional applications do this using multi-threading. Until recently however, creating true multi-threaded applications wasn't a simple task and wasn't available to most developers. Thankfully, multi-threading is a fundamental feature of the .NET Framework and developers can write multi-threaded applications with relative ease, no matter whether they use managed C++ code, C#, VB.NET, or any other .NET language.


advertisement
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.

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:

Interlocked.Increment(Me.Counter)

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.



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap
Thanks for your registration, follow us on our social networks to keep up-to-date