Multithreading 101

I l @ ve RuBoard

Each application has a main thread of execution. This main thread executes the application code in some specific order; whether based on user interaction or in response to a Windows message. What happens when more than one task or section of code needs to be executed? The order of execution will depend on how the single-threaded application is developed, but only one task at a time will be executed.

Under the Windows environment, it is possible for an application to spawn several threads and to control the interaction of those threads. This allows an application to handle multiple tasks concurrently. Consider your favorite Web browser. When downloading a file, the Web browser will spawn a thread to handle that task and allow you to navigate to another Web page while the download continues. Without the ability to create and control multiple threads within an application, the task of downloading the file from the Web would consume the main application thread. Under such a circumstance, the Web browser would be unable to perform any other task until the file download was complete.

The creation of multithreaded applications can improve performance of an application, but the addition of new execution threads adds a new level of complexity to the design, development, and debugging of such applications. In addition, it is not always a performance gain to start creating new threads without first evaluating the use of the application architecture.

Application Thread

The .NET architecture creates a single thread under an application domain to execute an application. The Main method of a class represents the entry point of the program and also the entry point of the application thread. Each thread created within the application thread is considered a child of the parent thread. This same notion applies to the application thread because the application thread is a child thread of the Application Domain . When the parent thread terminates, all child threads also will terminate. It is important to ensure that all child threads have been properly terminated and resources have been released before terminating the parent thread.

Worker Threads

The most common type of thread is a worker thread. A worker thread is used to perform some type of background processing that would otherwise occupy the application thread. Examples of such background processing include file downloading, background printing, and auto save features. When used properly, worker threads allow the main application thread to respond to application messages and give the appearance of a more responsive application.

Creating a Worker Thread

Like the application thread, a worker thread executes a section of code within the program. A basic worker thread example can be used to illustrate this. Listing 5.4.1 shows the basic construction of a worker thread.

Listing 5.4.1 Basic worker thread
 1: using System;  2: using System.Threading;  3:  4: namespace BasicWorkerThread  5: {  6:  7:     class Class1  8:     {  9: 10:         static void WorkerThread( ) 11:         { 12:             Console.WriteLine("Hello from WorkerThread"); 13:         } 14: 15:         static void Main(string[] args) 16:         { 17: 18:              //Create the Worker Thread 19:              Thread WT = new Thread( new ThreadStart( WorkerThread ) ); 20: 21:              //Start the Thread 22:              WT.Start( ); 23: 24:              //end the application 25:              Console.WriteLine("Press enter to exit"); 26:              Console.ReadLine( ); 27:         } 28:     } 29: } 30: 

The first thing to notice about Listing 5.4.1 is the using directive to import the System.Threading namespace. All thread- related classes are found within this namespace. To create a thread, there needs to be a method to act as the thread delegate. That's right: .NET threading uses a delegate to define the method a new thread will use. In the case of the example in Listing 5.4.1, the static method WorkerThread matches the delegate definition required for threads. On line 19, a new Thread object is created. Notice that the constructor for a Thread takes as a parameter a ThreadStart delegate. As mentioned earlier, the ThreadStart delegate defines which method the newly created thread will execute. Thread delegates have only a single possible signature, which consists of a void return and an empty argument list.

Those of you familiar with traditional Win32 threads, or POSIX threads, might be wondering why there are no arguments to a thread. In Win32 threading, a thread function takes a LPVOID (long pointer to a void) as a parameter. This allows any possible parameter to be passed to the thread function. Unlike Win32 threads, .NET threads can be instance based. That is, a nonstatic method of an object can be used for the thread. When a non-static method is used, that method and thread have access to all fields, properties, and methods of that object instance. This represents a significant difference from static Win32 -type threads. Listing 5.4.2 shows a thread accessing instance data within a class.

Listing 5.4.2 Instance Thread
 1: using System;  2: using System.Threading;  3:  4: namespace InstanceThread  5: {  6:     class Bank  7:     {  8:  9:         private string bankName = "First Bank"; 10: 11:         public void WorkerThread( ) 12:         { 13:             //Access instance member bankName 14:             Console.WriteLine("Bank Name = { 0} ", this.bankName ); 15:         } 16: 17:         public void Run( ) 18:         { 19:            Thread WT = new Thread( new ThreadStart( this.WorkerThread ) ); 20:             WT.Start( ); 21:         } 22: 23: 24:         static void Main(string[] args) 25:         { 26:             Bank b = new Bank( ); 27:             b.Run( ); 28:         } 29:     } 30: } 

The code in Listing 5.4.2 is not much different than that found in Listing 5.4.1. In this example, the thread delegate WorkerThread has instance-based access to the private field bankName . Such object-aware threads are a major departure from traditional Win32 based threads. .NET provides a closer notion of object-aware threads than ever before, but it's not quite perfect. There does not exist a base class from which to derive classes that will act as threads. In the .NET framework, the System.Threading.Thread base class is sealed and, as such, is not meant to serve as a base class.

ThreadStatic Attribute

The ThreadStatic attribute provides a mechanism for creating static fields that are thread specific. Each thread created will have its own copy of any field that is attributed with the ThreadStatic attribute. It's also important to note that any field marked with the Thread Static attribute will be zeroed out for threads, regardless of the initial assigned value. The static field will retain the initial value only for the main application thread. Listing 5.4.3 illustrates the use of the ThreadStatic attribute and the effect it has on static fields.

Listing 5.4.3 ThreadStatic Attribute
 1: using System;  2: using System.Threading;  3:  4:  5:  6: public class Task {  7:  8:     [ThreadStatic] static int m_nId = 10;  9: 10:     public string             m_strThreadName; 11: 12:     public void Run( string ThreadName ) { 13:         this.m_strThreadName = ThreadName; 14:         Thread T = new Thread( new ThreadStart( threadProc ) ); 15:         T.Start( ); 16:     } 17: 18:     public void threadProc( ) { 19: 20:         Console.WriteLine( "Thread { 0}  is running", m_strThreadName ); 21: 22:         //loop and increment the m_nId static field 23:         for(int i = 0; i < 10; i++ ) 24:         Console.WriteLine("Thread { 0}  : m_nId = { 1} ", 25:                            this.m_strThreadName, m_nId++ ); 26:     } 27: } 28: 29: 30: public class ThreadApp { 31: 32:     public static void Main( ) { 33: 34:         //Create 2 worker Tasks 35:         Task t1 = new Task( ); 36:         Task t2 = new Task( ); 37: 38:         t1.Run( "Worker 1" ); 39:         t2.Run( "Worker 2" ); 40: 41:         //Execute on the Main thread 42:         Task t3 = new Task( ); 43:         t3.m_strThreadName = "Main Thread"; 44:         t3.threadProc( ); 45:     } 46: } 

The output from Listing 5.4.3 will, of course, depend on the order in which each thread is scheduled to run and any thread switching that takes place. However, you can see that each thread, when executed, has a static member m_nId with an initial value of zero. When the threadProc member is executed from the context of the main application thread (line 44), the static field retains its initial value of 10. The ThreadStatic attribute allows each thread to retain its own static member data. If this is not the desired effect, the ThreadStatic attribute should not be used, in which case, any static member will be available to all threads and its value shared among them.

Join ”Bringing Threads Together

It often is necessary to spawn one or more threads and have the parent thread wait for those threads to complete before continuing execution. This can be accomplished easily by making use of the Join instance method of a thread. When the Join method is called, the calling thread will suspend until the worker thread completes. After the worker thread has exited, execution will continue within the calling thread (see Listing 5.4.4).

Listing 5.4.4 Using Join
 1: using System;  2: using System.Threading;  3:  4:  5:  6: public class JoinTest {  7:  8:  9:     public static void ThreadProc( ) { 10: 11:         Console.WriteLine("Thread Active"); 12: 13:         Thread.Sleep( 30 * 1000 ); 14: 15:         Console.WriteLine("Thread Exiting"); 16:     } 17: 18: 19:     public static void Main() { 20:         Thread T = new Thread( new ThreadStart( ThreadProc ) ); 21:         T.Start( ); 22:         T.Join( ); 23:         Console.WriteLine("After Join"); 24:     } 25: } 
I l @ ve RuBoard


C# and the .NET Framework. The C++ Perspective
C# and the .NET Framework
ISBN: 067232153X
EAN: 2147483647
Year: 2001
Pages: 204

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net