A thread represents a single flow of execution logic in a program. Some programs never need more than a single thread to execute efficiently, but many do. Threading in .NET allows you to build responsive and efficient applications. Many applications need to perform multiple actions at the same time (like supporting simultaneous user interface interaction and data processing)—threading allows the developer to provide this capability. Once you have multiple threads of execution in your application, you need to start thinking about what data in your application needs to be protected from multiple access, what data could cause threads to develop an interdependency that could lead to deadlocking (where Thread A has a resource that Thread B is waiting for and Thread B has a resource that Thread A is waiting for), and how to store data relative to the individual threads. We will explore some of these issues to help you take advantage of this wonderful capability of the .NET Framework, while explaining the areas to beware and items to keep in mind while designing and creating your multithreaded application.
Static fields, by default, are shared between threads within an application domain. You need to allow each thread to have its own nonshared copy of a static field, so that this static field can be updated on a per-thread basis.
Use
ThreadStaticAttribute
to mark any
static
fields as not being shareable between
threads:
using System; using System.Threading; public class Foo { [ThreadStaticAttribute( )] public static string bar = "Initialized string"; }
By default, static fields are shared between the threads that access
these fields in an application domain as a whole. To see this,
we’ll create a class with a static field called
bar
and a static method used to access and display
the value contained in this static
field:
using System; using System.Threading; public class ThreadStaticField { public static string bar = "Initialized string"; public static void DisplayStaticFieldValue( ) { Console.WriteLine(Thread.CurrentThread.GetHashCode( ) + " contains static field value of: " + ThreadStaticField.bar); } }
Next, create a test method that accesses this static field both on the current thread and on a newly spawned thread:
public void TestStaticField( ) { ThreadStaticField.DisplayStaticFieldValue( ); Thread newStaticFieldThread = new Thread(new ThreadStart( ThreadStaticField.DisplayStaticFieldValue)); newStaticFieldThread.Start( ); ThreadStaticField.DisplayStaticFieldValue( ); }
This code displays output that resembles the following:
21 contains static field value of: Initialized string 21 contains static field value of: Initialized string 23 contains static field value of: Initialized string
The current thread’s hash value is
21
and the new thread’s hash
value is 23
. Notice that both threads are
accessing the same static bar
field. Next, add the
ThreadStaticAttribute
to the static field:
public class ThreadStaticField
{
[ThreadStaticAttribute( )]
public static string bar = "Initialized string";
public static void DisplayStaticFieldValue( )
{
//bar = Thread.CurrentThread.ThreadState.ToString( );
Console.WriteLine(Thread.CurrentThread.GetHashCode( ) +
" contains static field value of: " + ThreadStaticField.bar);
}
}
Now, output resembling the following is displayed:
21 contains static field value of: Initialized string 21 contains static field value of: Initialized string 23 contains static field value of:
Notice that the new thread returns a null
for the
value of the static bar
field. This is the
expected behavior. The bar
field is only
initialized in the first thread that accesses it. On all other
threads, this field is initialized to null
.
Therefore, it is imperative that you initialize the
bar
field on all threads before it is used.
Remember to initialize any static field that is marked with
ThreadStaticAttribute
before it is used on any
thread. That is, this field should be initialized in the method
passed in to the ThreadStart
delegate. You should
make sure to not initialize the static field using a field
initializer as is shown in the prior code since only one thread gets
to see that initial value.
The bar
field is initialized to the
"Initialized string
" string literal before it is
used on the first thread that accesses this field. In the previous
test code, the bar
field was accessed first, and,
therefore, it was initialized, on the current thread. Suppose we were
to remove the first line of the TestStaticField
method, as shown here:
public void TestStaticField( )
{
// ThreadStaticField.DisplayStaticFieldValue( );
Thread newStaticFieldThread =
new Thread(new ThreadStart(ThreadStaticField.DisplayStaticFieldValue));
newStaticFieldThread.Start( );
ThreadStaticField.DisplayStaticFieldValue( );
}
This code now displays similar output to the following:
21 contains static field value of: 23 contains static field value of: Initialized string
The current thread does not access the bar
field
first and therefore does not initialize it. However, when the new
thread accesses it first, it does initialize it.
Note that adding a static constructor to initialize the static field marked with this attribute will still follow the same behavior. Static constructors are executed only one time per application domain.