Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp 2005 Recipes (2006) [eng]

.pdf
Скачиваний:
48
Добавлен:
16.08.2013
Размер:
4.04 Mб
Скачать

118 C H A P T E R 4 T H R E A D S, P R O C E S S E S, A N D S Y N C H R O N I Z AT I O N

// items from the work queue.

private static void ProcessWorkItems()

{

// A local variable to hold the work item taken from the work queue. string workItem = null;

TraceMsg("Thread started, processing items from queue...");

// Process items from the work queue until termination is signaled. while (processWorkItems)

{

// Obtain the lock on the work queue. Monitor.Enter(workQueue);

try

{

//Pop the next work item and process it, or wait if none

//is available.

if (workQueue.Count == 0)

{

TraceMsg("No work items, waiting...");

// Wait until Pulse is called on the workQueue object. Monitor.Wait(workQueue);

}

else

{

// Obtain the next work item. workItem = workQueue.Dequeue();

}

}

finally

{

// Always release the lock. Monitor.Exit(workQueue);

}

// Process the work item if one was obtained. if (workItem != null)

{

//Obtain a lock on the console and display a series

//of messages.

lock (consoleGate)

{

for (int i = 0; i < 5; i++)

{

TraceMsg("Processing " + workItem); Thread.Sleep(200);

}

}

// Reset the status of the local variable. workItem = null;

}

}

// This will be reached only if processWorkItems is false.

C H A P T E R 4 T H R E A D S, P R O C E S S E S, A N D S Y N C H R O N I Z AT I O N

119

TraceMsg("Terminating.");

}

public static void Main()

{

TraceMsg("Starting worker threads.");

//Add an initial work item to the work queue. lock (workQueue)

{

workQueue.Enqueue("Work Item 1");

}

//Create and start three new worker threads running the

//ProcessWorkItems method.

for (int count = 0; count < 3; count++)

{

(new Thread(ProcessWorkItems)).Start();

}

Thread.Sleep(1500);

//The first time the user presses Enter, add a work item and

//activate a single thread to process it.

TraceMsg("Press Enter to pulse one waiting thread.");

Console.ReadLine();

// Acquire a lock on the workQueue object. lock (workQueue)

{

//Add a work item. workQueue.Enqueue("Work Item 2.");

//Pulse 1 waiting thread. Monitor.Pulse(workQueue);

}

Thread.Sleep(2000);

//The second time the user presses Enter, add three work items and

//activate three threads to process them.

TraceMsg("Press Enter to pulse three waiting threads.");

Console.ReadLine();

// Acquire a lock on the workQueue object. lock (workQueue)

{

// Add work items to the work queue, and activate worker threads. workQueue.Enqueue("Work Item 3.");

Monitor.Pulse(workQueue); workQueue.Enqueue("Work Item 4."); Monitor.Pulse(workQueue); workQueue.Enqueue("Work Item 5."); Monitor.Pulse(workQueue);

}

Thread.Sleep(3500);

120C H A P T E R 4 T H R E A D S, P R O C E S S E S, A N D S Y N C H R O N I Z AT I O N

//The third time the user presses Enter, signal the worker threads

//to terminate and activate them all.

TraceMsg("Press Enter to pulse all waiting threads.");

Console.ReadLine();

// Acquire a lock on the workQueue object. lock (workQueue)

{

//Signal that threads should terminate. processWorkItems = false;

//Pulse all waiting threads. Monitor.PulseAll(workQueue);

}

Thread.Sleep(1000);

// Wait to continue.

TraceMsg("Main method complete. Press Enter."); Console.ReadLine();

}

}

}

4-8. Synchronize the Execution of Multiple

Threads Using an Event

Problem

You need a mechanism to synchronize the execution of multiple threads in order to coordinate their activities or access to shared resources.

Solution

Use the EventWaitHandle, AutoResetEvent, and ManualResetEvent classes from the System.Threading namespace.

How It Works

The EventWaitHandle, AutoResetEvent, and ManualResetEvent classes provide similar functionality. In fact, although the EventWaitHandle is new to .NET 2.0, it is the base class from which the

AutoResetEvent and ManualResetEvent classes are derived. (EventWaitHandle inherits from

System.Threading.WaitHandle and allows you to create named events.) All three event classes allow you to synchronize multiple threads by manipulating the state of the event between two possible values: signaled and unsignaled.

Threads requiring synchronization call static or inherited methods of the WaitHandle abstract base class (summarized in Table 4-1) to test the state of one or more event objects. If the events are signaled when tested, the thread continues to operate unhindered. If the events are unsignaled, the

C H A P T E R 4 T H R E A D S, P R O C E S S E S, A N D S Y N C H R O N I Z AT I O N

121

thread enters a WaitSleepJoin state, blocking until one or more of the events become signaled or when a given time-out expires.

Table 4-1. WaitHandle Methods for Synchronizing Thread Execution

Method

Description

WaitOne

Causes the calling thread to enter a WaitSleepJoin state and wait for a specific

 

WaitHandle derived object to be signaled. You can also specify a time-out value.

 

The WaitingExample method in recipe 4-2 demonstrates how to use the WaitOne

 

method.

WaitAny

A static method that causes the calling thread to enter a WaitSleepJoin state

 

and wait for any one of the objects in a WaitHandle array to be signaled. You can

 

also specify a time-out value.

WaitAll

A static method that causes the calling thread to enter a WaitSleepJoin state

 

and wait for all the WaitHandle objects in a WaitHandle array to be signaled. You

 

can also specify a time-out value. The WaitAllExample method in recipe 4-2

 

demonstrates how to use the WaitAll method.

SignalAndWait

A static method that causes the calling thread to signal a specified event

 

object and then wait on a specified event object. The signal and wait operations

 

are carried out as an atomic operation. You can also specify a time-out value.

 

SignalAndWait is new to .NET 2.0.

 

 

The key differences between the three event classes are how they transition from a signaled to an unsignaled state and their visibility. Both the AutoResetEvent and ManualResetEvent classes are local to the process in which they are declared. To signal an AutoResetEvent class, call its Set method, which will release only one thread that is waiting on the event. The AutoResetEvent class will then automatically return to an unsignaled state. The code in recipe 4-4 demonstrates how to use an

AutoResetEvent class.

The ManualResetEvent class must be manually switched back and forth between signaled and unsignaled states using its Set and Reset methods. Calling Set on a ManualResetEvent class will set it to a signaled state, releasing all threads that are waiting on the event. Only by calling Reset does the ManualResetEvent class become unsignaled.

You can configure the EventWaitHandle class to operate in a manual or automatic reset mode, making it possible to act like either the AutoResetEvent class or the ManualResetEvent class. When you create the EventWaitHandle, you pass a value of the System.Threading.EventResetMode enumeration to configure the mode in which the EventWaitHandle will function; the two possible values are AutoReset and ManualReset. The unique benefit of the EventWaitHandle class is that it is not constrained to the local process. When you create an EventWaitHandle class, you can associate a name with it that makes it accessible to other processes, including nonmanaged Win32 code. This allows you to synchronize the activities of threads across process and application domain boundaries and synchronize access to resources that are shared by multiple processes. To obtain a reference to an existing named EventWaitHandle, call the static method EventWaitHandle.OpenExisting, and specify the name of the event.

The Code

The following example demonstrates how to use a named EventWaitHandle in manual mode that is initially signaled. A thread is spawned that waits on the event and then displays a message to the console—repeating the process every 2 seconds. When you press Enter, you toggle the event between a signaled and a nonsignaled state.

122C H A P T E R 4 T H R E A D S, P R O C E S S E S, A N D S Y N C H R O N I Z AT I O N

using System;

using System.Threading;

namespace Apress.VisualCSharpRecipes.Chapter04

{

class Recipe04_08

{

//Boolean to signal that the second thread should terminate. static bool terminate = false;

//A utility method for displaying useful trace information to the

//console along with details of the current thread.

private static void TraceMsg(string msg)

{

Console.WriteLine("[{0,3}] - {1} : {2}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.ffff"), msg);

}

//Declare the method that will be executed on the separate thread.

//The method waits on the EventWaitHandle before displaying a message

//to the console and then waits two seconds and loops.

private static void DisplayMessage()

{

// Obtain a handle to the EventWaitHandle with the name "EventExample". EventWaitHandle eventHandle =

EventWaitHandle.OpenExisting("EventExample");

TraceMsg("DisplayMessage Started.");

while (!terminate)

{

//Wait on the EventWaitHandle, time-out after two seconds. WaitOne

//returns true if the event is signaled; otherwise, false. The

//first time through, the message will be displayed immediately

//because the EventWaitHandle was created in a signaled state.

if (eventHandle.WaitOne(2000, true))

{

TraceMsg("EventWaitHandle In Signaled State.");

}

else

{

TraceMsg("WaitOne Timed Out -- " + "EventWaitHandle In Unsignaled State.");

}

Thread.Sleep(2000);

}

TraceMsg("Thread Terminating.");

}

public static void Main()

C H A P T E R 4 T H R E A D S, P R O C E S S E S, A N D S Y N C H R O N I Z AT I O N

123

{

//Create a new EventWaitHandle with an initial signaled state, in

//manual mode, with the name "EventExample".

using (EventWaitHandle eventWaitHandle =

new EventWaitHandle(true, EventResetMode.ManualReset, "EventExample"))

{

//Create and start a new thread running the DisplayMesssage

//method.

TraceMsg("Starting DisplayMessageThread."); Thread trd = new Thread(DisplayMessage); trd.Start();

//Allow the EventWaitHandle to be toggled between a signaled and

//unsignaled state up to three times before ending.

for (int count = 0; count < 3; count++)

{

//Wait for Enter to be pressed. Console.ReadLine();

//You need to toggle the event. The only way to know the

//current state is to wait on it with a 0 (zero) time-out

//and test the result.

if (eventWaitHandle.WaitOne(0, true))

{

TraceMsg("Switching Event To UnSignaled State.");

// Event is signaled, so unsignal it. eventWaitHandle.Reset();

}

else

{

TraceMsg("Switching Event To Signaled State.");

// Event is unsignaled, so signal it. eventWaitHandle.Set();

}

}

//Terminate the DisplayMessage thread, and wait for it to

//complete before disposing of the EventWaitHandle. terminate = true;

eventWaitHandle.Set();

trd.Join(5000);

}

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();

}

}

}

124 C H A P T E R 4 T H R E A D S, P R O C E S S E S, A N D S Y N C H R O N I Z AT I O N

4-9. Synchronize the Execution of Multiple Threads

Using a Mutex

Problem

You need to coordinate the activities of multiple threads (possibly across process boundaries) to ensure the efficient use of shared resources or to ensure several threads are not updating the same shared resource at the same time.

Solution

Use the System.Threading.Mutex class.

How It Works

The Mutex has a similar purpose to the Monitor discussed in recipe 4-7—it provides a means to ensure only a single thread has access to a shared resource or section of code at any given time. However, unlike the Monitor, which is implemented fully within managed code, the Mutex is a wrapper around an operating system synchronization object. This, and because Mutexes can be given names, means you can use a Mutex to synchronize the activities of threads across process boundaries, even with threads running in nonmanaged Win32 code.

Like the EventWaitHandle, AutoResetEvent, and ManualResetEvent classes discussed in recipe 4-8, the Mutex is derived from System.Threading.WaitHandle and enables thread synchronization in a similar fashion. A Mutex is in either a signaled state or an unsignaled state. A thread acquires ownership of the Mutex at construction or by using one of the methods listed in Table 4-1. If a thread has ownership of the Mutex, the Mutex is unsignaled, meaning other threads will block if they try to acquire ownership. Ownership of the Mutex is released by the owning thread calling the Mutex.ReleaseMutex method, which signals the Mutex and allows another thread to acquire ownership. A thread may acquire ownership of a Mutex any number of times without problems, but it must release the Mutex an equal number of times to free it and make it available for another thread to acquire. If the thread with ownership of a Mutex terminates normally, the Mutex becomes signaled, allowing another thread to acquire ownership.

The Code

The following example demonstrates how to use a named Mutex to limit access to a shared resource (the console) to a single thread at any given time:

using System;

using System.Threading;

namespace Apress.VisualCSharpRecipes.Chapter04

{

class Recipe04_09

{

//Boolean to signal that the second thread should terminate. static bool terminate = false;

//A utility method for displaying useful trace information to the

//console along with details of the current thread.

private static void TraceMsg(string msg)

C H A P T E R 4 T H R E A D S, P R O C E S S E S, A N D S Y N C H R O N I Z AT I O N

125

{

Console.WriteLine("[{0,3}] - {1} : {2}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.ffff"), msg);

}

//Declare the method that will be executed on the separate thread.

//In a loop the method waits to obtain a Mutex before displaying a

//message to the console and then waits one second before releasing the

//Mutex.

private static void DisplayMessage()

{

//Obtain a handle to the Mutex with the name "MutexExample".

//Do not attempt to take ownership immediately.

using (Mutex mutex = new Mutex(false, "MutexExample"))

{

TraceMsg("Thread started.");

while (!terminate)

{

// Wait on the Mutex. mutex.WaitOne();

TraceMsg("Thread owns the Mutex.");

Thread.Sleep(1000);

TraceMsg("Thread releasing the Mutex.");

//Release the Mutex. mutex.ReleaseMutex();

//Sleep a little to give another thread a good chance of

//acquiring the Mutex.

Thread.Sleep(100);

}

TraceMsg("Thread terminating.");

}

}

public static void Main()

{

// Create a new Mutex with the name "MutexExample". using (Mutex mutex = new Mutex(false, "MutexExample"))

{

TraceMsg("Starting threads -- press Enter to terminate.");

//Create and start three new threads running the

//DisplayMesssage method.

Thread trd1 = new Thread(DisplayMessage); Thread trd2 = new Thread(DisplayMessage); Thread trd3 = new Thread(DisplayMessage); trd1.Start();

trd2.Start();

trd3.Start();

126C H A P T E R 4 T H R E A D S, P R O C E S S E S, A N D S Y N C H R O N I Z AT I O N

//Wait for Enter to be pressed. Console.ReadLine();

//Terminate the DisplayMessage threads, and wait for them to

//complete before disposing of the Mutex.

terminate = true; trd1.Join(5000); trd2.Join(5000); trd3.Join(5000);

}

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();

}

}

}

Note Recipe 4-17 demonstrates how to use a named Mutex as a means to ensure only a single instance of an application can be started at any given time.

4-10. Synchronize the Execution of Multiple

Threads Using a Semaphore

Problem

You need to control the number of threads that can access a shared resource or section of code concurrently.

Solution

Use the System.Threading.Semaphore class.

How It Works

The Semaphore is another synchronization class derived from the System.Threading.WaitHandle class. The Semaphore is new in .NET 2.0 but will be familiar to those with Win32 programming experience. The purpose of the Semaphore is to allow a specified maximum number of threads to access a shared resource or section of code concurrently.

As with the other synchronization classes derived from WaitHandle (discussed in recipe 4-8 and recipe 4-9), a Semaphore is either in a signaled state or in an unsignaled state. Threads wait for the Semaphore to become signaled using the methods described in Table 4-1. The Semaphore maintains a count of the active threads it has allowed through and automatically switches to an unsignaled state once the maximum number of threads is reached. To release the Semaphore and allow other waiting threads the opportunity to act, a thread calls the Release method on the Semaphore object. A thread may acquire ownership of the Semaphore more than once, reducing the maximum number of threads that can be active concurrently, and must call Release the same number of times to fully release it.

C H A P T E R 4 T H R E A D S, P R O C E S S E S, A N D S Y N C H R O N I Z AT I O N

127

The Code

The following example demonstrates how to use a named Semaphore to limit access to a shared resource (the console) to two threads at any given time. The code is similar to that used in recipe 4-9 but substitutes a Semaphore for the Mutex.

using System;

using System.Threading;

namespace Apress.VisualCSharpRecipes.Chapter04

{

class Recipe04_10

{

//Boolean to signal that the second thread should terminate. static bool terminate = false;

//A utility method for displaying useful trace information to the

//console along with details of the current thread.

private static void TraceMsg(string msg)

{

Console.WriteLine("[{0,3}] - {1} : {2}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.ffff"), msg);

}

//Declare the method that will be executed on the separate thread.

//In a loop the method waits to obtain a Semaphore before displaying a

//message to the console and then waits one second before releasing the

//Semaphore.

private static void DisplayMessage()

{

// Obtain a handle to the Semaphore with the name "SemaphoreExample". using (Semaphore sem = Semaphore.OpenExisting("SemaphoreExample"))

{

TraceMsg("Thread started.");

while (!terminate)

{

// Wait on the Semaphore. sem.WaitOne();

TraceMsg("Thread owns the Semaphore.");

Thread.Sleep(1000);

TraceMsg("Thread releasing the Semaphore.");

//Release the Semaphore. sem.Release();

//Sleep a little to give another thread a good chance of

//acquiring the Semaphore.

Thread.Sleep(100);

}

TraceMsg("Thread terminating.");

}

}

Соседние файлы в предмете Программирование на C++