The advantage of thread processing is that you can create applications that use multiple execution threads. For example, a process can have a user interface thread that manages user interaction and a third thread that executes other tasks while the user interface thread waits for user input.
This tutorial describes various thread activities:
- Create and execute threads
- Thread Synchronization
- Interaction between threads
- Use thread pool
- Use mutex object to protect shared resources
Tutorial
This tutorial contains the following examples:
- Example 1: Create thread, start thread, and thread Interaction
- Example 2: Synchronize two threads: producer and user
- Example 3: Use a thread pool
- Example 4: Use a mutex object
Example 1: Create thread, start thread, and thread Interaction
This example shows how to create and start a thread, and shows the interaction between two threads running in the same process at the same time. Note that you do not need to stop or release the thread. This is automatically completed by the. NET Framework Public language runtime.
The program starts from creating an alpha-type object (oalpha) and a thread (othread) that references the beta method of the Alpha Class. Then start the thread. The isalive attribute of the thread allows the program to wait until the thread is initialized (created or allocated. The main thread is accessed through a thread, and the sleep method notifies the thread to discard its time slice and stop the execution within a certain number of milliseconds. Then the othread is stopped and connected. Joining a thread will cause the main thread to wait for it to die or wait for it to expire after the specified time. Finally, the program tries to restart othread, but it fails because the thread cannot be restarted after it is stopped (aborted. For information on temporarily stopping execution, see suspending thread execution.
// StopJoin.csusing System;using System.Threading;public class Alpha{ // This method that will be called when the thread is started public void Beta() { while (true) { Console.WriteLine("Alpha.Beta is running in its own thread."); } }};public class Simple{ public static int Main() { Console.WriteLine("Thread Start/Stop/Join Sample"); Alpha oAlpha = new Alpha(); // Create the thread object, passing in the Alpha.Beta method // via a ThreadStart delegate. This does not start the thread. Thread oThread = new Thread(new ThreadStart(oAlpha.Beta)); // Start the thread oThread.Start(); // Spin for a while waiting for the started thread to become // alive: while (!oThread.IsAlive); // Put the Main thread to sleep for 1 millisecond to allow oThread // to do some work: Thread.Sleep(1); // Request that oThread be stopped oThread.Abort(); // Wait until oThread finishes. Join also has overloads // that take a millisecond interval or a TimeSpan object. oThread.Join(); Console.WriteLine(); Console.WriteLine("Alpha.Beta has finished"); try { Console.WriteLine("Try to restart the Alpha.Beta thread"); oThread.Start(); } catch (ThreadStateException) { Console.Write("ThreadStateException trying to restart Alpha.Beta. "); Console.WriteLine("Expected since aborted threads cannot be restarted."); } return 0; }}
Output example
Thread Start/Stop/Join SampleAlpha.Beta is running in its own thread.Alpha.Beta is running in its own thread.Alpha.Beta is running in its own thread.......Alpha.Beta has finishedTry to restart the Alpha.Beta threadThreadStateException trying to restart Alpha.Beta. Expected since aborted threads cannot be restarted.
Example 2: Synchronize two threads: producer and user
The following example shows how to use the C # Lock keyword and the pulse method of the monitor object to complete synchronization. The pulse method notifies you that the status of the thread object in the waiting queue has changed. (For more information about pulses, see the monitor. Pulse method ).
In this example, create a cell object, which has two methods: readfromcell and writetocell. Create two other objects from the cellprod and cellcons classes; both objects have threadrun methods that call readfromcell and writetocell. The synchronization can be completed by waiting for the "pulse" from the monitor object to arrive in sequence. That is to say, first produce an item (the user waits for the pulse at this time), then a pulse occurs, and then the user uses the generated item (the maker waits for the pulse at this time), and so on.
// MonitorSample.cs// This example shows use of the following methods of the C# lock keyword// and the Monitor class // in threads:// Monitor.Pulse(Object)// Monitor.Wait(Object)using System;using System.Threading;public class MonitorSample{ public static void Main(String[] args) { int result = 0; // Result initialized to say there is no error Cell cell = new Cell( ); CellProd prod = new CellProd(cell, 20); // Use cell for storage, // produce 20 items CellCons cons = new CellCons(cell, 20); // Use cell for storage, // consume 20 items Thread producer = new Thread(new ThreadStart(prod.ThreadRun)); Thread consumer = new Thread(new ThreadStart(cons.ThreadRun)); // Threads producer and consumer have been created, // but not started at this point. try { producer.Start( ); consumer.Start( ); producer.Join( ); // Join both threads with no timeout // Run both until done. consumer.Join( ); // threads producer and consumer have finished at this point. } catch (ThreadStateException e) { Console.WriteLine(e); // Display text of exception result = 1; // Result says there was an error } catch (ThreadInterruptedException e) { Console.WriteLine(e); // This exception means that the thread // was interrupted during a Wait result = 1; // Result says there was an error } // Even though Main returns void, this provides a return code to // the parent process. Environment.ExitCode = result; }}public class CellProd{ Cell cell; // Field to hold cell object to be used int quantity = 1; // Field for how many items to produce in cell public CellProd(Cell box, int request) { cell = box; // Pass in what cell object to be used quantity = request; // Pass in how many items to produce in cell } public void ThreadRun( ) { for(int looper=1; looper<=quantity; looper++) cell.WriteToCell(looper); // "producing" }}public class CellCons{ Cell cell; // Field to hold cell object to be used int quantity = 1; // Field for how many items to consume from cell public CellCons(Cell box, int request) { cell = box; // Pass in what cell object to be used quantity = request; // Pass in how many items to consume from cell } public void ThreadRun( ) { int valReturned; for(int looper=1; looper<=quantity; looper++) // Consume the result by placing it in valReturned. valReturned=cell.ReadFromCell( ); }}public class Cell{ int cellContents; // Cell contents bool readerFlag = false; // State flag public int ReadFromCell( ) { lock(this) // Enter synchronization block { if (!readerFlag) { // Wait until Cell.WriteToCell is done producing try { // Waits for the Monitor.Pulse in WriteToCell Monitor.Wait(this); } catch (SynchronizationLockException e) { Console.WriteLine(e); } catch (ThreadInterruptedException e) { Console.WriteLine(e); } } Console.WriteLine("Consume: {0}",cellContents); readerFlag = false; // Reset the state flag to say consuming // is done. Monitor.Pulse(this); // Pulse tells Cell.WriteToCell that // Cell.ReadFromCell is done. } // Exit synchronization block return cellContents; } public void WriteToCell(int n) { lock(this) // Enter synchronization block { if (readerFlag) { // Wait until Cell.ReadFromCell is done consuming. try { Monitor.Wait(this); // Wait for the Monitor.Pulse in // ReadFromCell } catch (SynchronizationLockException e) { Console.WriteLine(e); } catch (ThreadInterruptedException e) { Console.WriteLine(e); } } cellContents = n; Console.WriteLine("Produce: {0}",cellContents); readerFlag = true; // Reset the state flag to say producing // is done Monitor.Pulse(this); // Pulse tells Cell.ReadFromCell that // Cell.WriteToCell is done. } // Exit synchronization block }}
Output example
Produce: 1Consume: 1Produce: 2Consume: 2Produce: 3Consume: 3......Produce: 20Consume: 20
Example 3: Use a thread pool
The following example shows how to use the thread pool. First, create the manualresetevent object, which enables the program to know when the thread pool will run all the work items. Next, try to add a thread to the thread pool. If it is successfully added, add the remaining threads (4 in this example ). Then, the thread pool puts the work item into the available thread. Call the waitone Method on eventx, which waits for the rest of the program until the event is triggered using eventx. Set. Finally, the program prints the load on the thread (the thread that actually executes a specific work item ).
// SimplePool.cs// Simple thread pool exampleusing System;using System.Collections;using System.Threading;// Useful way to store info that can be passed as a state on a work itempublic class SomeState{ public int Cookie; public SomeState(int iCookie) { Cookie = iCookie; }}public class Alpha{ public Hashtable HashCount; public ManualResetEvent eventX; public static int iCount = 0; public static int iMaxCount = 0; public Alpha(int MaxCount) { HashCount = new Hashtable(MaxCount); iMaxCount = MaxCount; } // Beta is the method that will be called when the work item is // serviced on the thread pool. // That means this method will be called when the thread pool has // an available thread for the work item. public void Beta(Object state) { // Write out the hashcode and cookie for the current thread Console.WriteLine(" {0} {1} :", Thread.CurrentThread.GetHashCode(), ((SomeState)state).Cookie); // The lock keyword allows thread-safe modification // of variables accessible across multiple threads. Console.WriteLine( "HashCount.Count=={0}, Thread.CurrentThread.GetHashCode()=={1}", HashCount.Count, Thread.CurrentThread.GetHashCode()); lock (HashCount) { if (!HashCount.ContainsKey(Thread.CurrentThread.GetHashCode())) HashCount.Add (Thread.CurrentThread.GetHashCode(), 0); HashCount[Thread.CurrentThread.GetHashCode()] = ((int)HashCount[Thread.CurrentThread.GetHashCode()])+1; } // Do some busy work. // Note: Depending on the speed of your machine, if you // increase this number, the dispersement of the thread // loads should be wider. int iX = 2000; Thread.Sleep(iX); // The Interlocked.Increment method allows thread-safe modification // of variables accessible across multiple threads. Interlocked.Increment(ref iCount); if (iCount == iMaxCount) { Console.WriteLine(); Console.WriteLine("Setting eventX "); eventX.Set(); } }}public class SimplePool{ public static int Main(string[] args) { Console.WriteLine("Thread Pool Sample:"); bool W2K = false; int MaxCount = 10; // Allow a total of 10 threads in the pool // Mark the event as unsignaled. ManualResetEvent eventX = new ManualResetEvent(false); Console.WriteLine("Queuing {0} items to Thread Pool", MaxCount); Alpha oAlpha = new Alpha(MaxCount); // Create the work items. // Make sure the work items have a reference to the signaling event. oAlpha.eventX = eventX; Console.WriteLine("Queue to Thread Pool 0"); try { // Queue the work items, which has the added effect of checking // which OS is running. ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta), new SomeState(0)); W2K = true; } catch (NotSupportedException) { Console.WriteLine("These API's may fail when called on a non-Windows 2000 system."); W2K = false; } if (W2K) // If running on an OS which supports the ThreadPool methods. { for (int iItem=1;iItem < MaxCount;iItem++) { // Queue the work items: Console.WriteLine("Queue to Thread Pool {0}", iItem); ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta),new SomeState(iItem)); } Console.WriteLine("Waiting for Thread Pool to drain"); // The call to exventX.WaitOne sets the event to wait until // eventX.Set() occurs. // (See oAlpha.Beta). // Wait until event is fired, meaning eventX.Set() was called: eventX.WaitOne(Timeout.Infinite,true); // The WaitOne won't return until the event has been signaled. Console.WriteLine("Thread Pool has been drained (Event fired)"); Console.WriteLine(); Console.WriteLine("Load across threads"); foreach(object o in oAlpha.HashCount.Keys) Console.WriteLine("{0} {1}", o, oAlpha.HashCount[o]); } return 0; }}
Output example
Note that the following output varies with the computer.
Thread Pool Sample:Queuing 10 items to Thread PoolQueue to Thread Pool 0Queue to Thread Pool 1......Queue to Thread Pool 9Waiting for Thread Pool to drain 98 0 :HashCount.Count==0, Thread.CurrentThread.GetHashCode()==98 100 1 :HashCount.Count==1, Thread.CurrentThread.GetHashCode()==100 98 2 :......Setting eventXThread Pool has been drained (Event fired)Load across threads101 2100 398 4102 1
Example 4: Use a mutex object
The mutex object can be used to protect shared resources from being accessed by multiple threads or processes at the same time. The status of the mutex object can be set to terminate (when it does not belong to any thread), or set to non-terminate (when it belongs to a thread ). At the same time, only one thread can own one mutex object. For example, to prevent two threads from writing data to the shared memory at the same time, each thread waits for the ownership of the mutex object before executing the code that accesses the shared memory. After the shared memory is written, the thread releases the mutex object.
This example illustrates how to use the mutex class, autoresetevent class, And waithandle class during thread processing. It also explains the methods used in processing mutex objects.
// Mutex.cs// Mutex object exampleusing System;using System.Threading;public class MutexSample{ static Mutex gM1; static Mutex gM2; const int ITERS = 100; static AutoResetEvent Event1 = new AutoResetEvent(false); static AutoResetEvent Event2 = new AutoResetEvent(false); static AutoResetEvent Event3 = new AutoResetEvent(false); static AutoResetEvent Event4 = new AutoResetEvent(false); public static void Main(String[] args) { Console.WriteLine("Mutex Sample ..."); // Create Mutex initialOwned, with name of "MyMutex". gM1 = new Mutex(true,"MyMutex"); // Create Mutex initialOwned, with no name. gM2 = new Mutex(true); Console.WriteLine(" - Main Owns gM1 and gM2"); AutoResetEvent[] evs = new AutoResetEvent[4]; evs[0] = Event1; // Event for t1 evs[1] = Event2; // Event for t2 evs[2] = Event3; // Event for t3 evs[3] = Event4; // Event for t4 MutexSample tm = new MutexSample( ); Thread t1 = new Thread(new ThreadStart(tm.t1Start)); Thread t2 = new Thread(new ThreadStart(tm.t2Start)); Thread t3 = new Thread(new ThreadStart(tm.t3Start)); Thread t4 = new Thread(new ThreadStart(tm.t4Start)); t1.Start( ); // Does Mutex.WaitAll(Mutex[] of gM1 and gM2) t2.Start( ); // Does Mutex.WaitOne(Mutex gM1) t3.Start( ); // Does Mutex.WaitAny(Mutex[] of gM1 and gM2) t4.Start( ); // Does Mutex.WaitOne(Mutex gM2) Thread.Sleep(2000); Console.WriteLine(" - Main releases gM1"); gM1.ReleaseMutex( ); // t2 and t3 will end and signal Thread.Sleep(1000); Console.WriteLine(" - Main releases gM2"); gM2.ReleaseMutex( ); // t1 and t4 will end and signal // Waiting until all four threads signal that they are done. WaitHandle.WaitAll(evs); Console.WriteLine("... Mutex Sample"); } public void t1Start( ) { Console.WriteLine("t1Start started, Mutex.WaitAll(Mutex[])"); Mutex[] gMs = new Mutex[2]; gMs[0] = gM1; // Create and load an array of Mutex for WaitAll call gMs[1] = gM2; Mutex.WaitAll(gMs); // Waits until both gM1 and gM2 are released Thread.Sleep(2000); Console.WriteLine("t1Start finished, Mutex.WaitAll(Mutex[]) satisfied"); Event1.Set( ); // AutoResetEvent.Set() flagging method is done } public void t2Start( ) { Console.WriteLine("t2Start started, gM1.WaitOne( )"); gM1.WaitOne( ); // Waits until Mutex gM1 is released Console.WriteLine("t2Start finished, gM1.WaitOne( ) satisfied"); Event2.Set( ); // AutoResetEvent.Set() flagging method is done } public void t3Start( ) { Console.WriteLine("t3Start started, Mutex.WaitAny(Mutex[])"); Mutex[] gMs = new Mutex[2]; gMs[0] = gM1; // Create and load an array of Mutex for WaitAny call gMs[1] = gM2; Mutex.WaitAny(gMs); // Waits until either Mutex is released Console.WriteLine("t3Start finished, Mutex.WaitAny(Mutex[])"); Event3.Set( ); // AutoResetEvent.Set() flagging method is done } public void t4Start( ) { Console.WriteLine("t4Start started, gM2.WaitOne( )"); gM2.WaitOne( ); // Waits until Mutex gM2 is released Console.WriteLine("t4Start finished, gM2.WaitOne( )"); Event4.Set( ); // AutoResetEvent.Set() flagging method is done }}
Sample output
Mutex Sample ... - Main Owns gM1 and gM2t1Start started, Mutex.WaitAll(Mutex[])t2Start started, gM1.WaitOne( )t3Start started, Mutex.WaitAny(Mutex[])t4Start started, gM2.WaitOne( ) - Main releases gM1t2Start finished, gM1.WaitOne( ) satisfiedt3Start finished, Mutex.WaitAny(Mutex[]) - Main releases gM2t1Start finished, Mutex.WaitAll(Mutex[]) satisfiedt4Start finished, gM2.WaitOne( )... Mutex Sample
Note that the output in this example may be different on each computer and each running time. The speed of the computer running this example and the operating system can affect the output sequence. In a multi-threaded environment, events may not occur in the expected order.