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

Visual CSharp 2005 Recipes (2006) [eng]

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

108 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

How It Works

It is often useful to execute a method at regular intervals. For example, you might need to clean

a data cache every 20 minutes. The Timer class makes the periodic execution of methods straightforward, allowing you to execute a method referenced by a TimerCallback delegate at specified intervals. The referenced method executes in the context of a thread from the thread pool. (See recipe 4-1 for notes on the appropriate use of thread-pool threads.)

When you create a Timer object, you specify two time intervals. The first value specifies the millisecond delay until the Timer first executes your method. Specify 0 to execute the method immediately, and specify System.Threading.Timeout.Infinite to create the Timer in an unstarted state. The second value specifies the interval in milliseconds; then the Timer will repeatedly call your method following the initial execution. If you specify a value of 0 or Timeout.Infinite, the Timer will execute the method only once (as long as the initial delay is not Timeout.Infinite). You can specify the time intervals as int, long, uint, or System.TimeSpan values.

Once you have created a Timer object, you can modify the intervals used by the timer using the Change method, but you cannot change the method that is called. When you have finished with a Timer object, you should call its Dispose method to free system resources held by the timer. Disposing of the Timer object cancels any method that is scheduled for execution.

The Code

The TimerExample class shown next demonstrates how to use a Timer object to call a method named TimerHandler. Initially, the Timer object is configured to call TimerHandler after 2 seconds and then at 1-second intervals. The example allows you to enter a new millisecond interval in the console, which is applied using the Timer.Change method.

using System;

using System.Threading;

namespace Apress.VisualCSharpRecipes.Chapter04

{

class Recipe04_03

{

public static void Main()

{

//Create the state object that is passed to the TimerHandler

//method when it is triggered. In this case, a message to display. string state = "Timer expired.";

Console.WriteLine("{0} : Creating Timer.",

DateTime.Now.ToString("HH:mm:ss.ffff"));

//Create a Timer that fires first after 2 seconds and then every

//second. Use an anonymous method for the timer expiry handler. using (Timer timer =

new Timer(delegate(object s) {Console.WriteLine("{0} : {1}",

DateTime.Now.ToString("HH:mm:ss.ffff"),s);

}

, state, 2000, 1000))

{

int period;

//Read the new timer interval from the console until the

//user enters 0 (zero). Invalid values use a default value

//of 0, which will stop the example.

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

109

do

{

try

{

period = Int32.Parse(Console.ReadLine());

}

catch (FormatException)

{

period = 0;

}

//Change the timer to fire using the new interval starting

//immediately.

if (period > 0) timer.Change(0, period); } while (period > 0);

}

// Wait to continue.

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

}

}

}

4-4. Execute a Method at a Specific Time

Problem

You need to execute a method in a separate thread at a specific time.

Solution

Declare a method containing the code you want to execute. The method’s signature must match that defined by the System.Threading.TimerCallback delegate; that is, it must return void and take a single object argument. Create a System.Threading.Timer object, and pass it the method you want to execute along with a state object that the timer will pass to your method when the timer expires. Calculate the time difference between the current time and the desired execution time, and configure the Timer object to fire once after this period of time.

How It Works

Executing a method at a particular time is often useful. For example, you might need to back up data at 1 a.m. daily. Although primarily used for calling methods at regular intervals, the Timer object also provides the flexibility to call a method at a specific time.

When you create a Timer object, you specify two time intervals. The first value specifies the millisecond delay until the Timer first executes your method. To execute the method at a specific time, you should set this value to the difference between the current time (System.DateTime.Now) and the desired execution time. The second value specifies the interval after which the Timer will repeatedly call your method following the initial execution. If you specify a value of 0, System. Threading.Timeout.Infinite, or TimeSpan(-1), the Timer object will execute the method only once. If you need the method to execute at a specific time every day, you can easily set this figure using TimeSpan.FromDays(1), which represents the number of milliseconds in 24 hours.

110 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

The Code

The following code demonstrates how to use a Timer object to execute a method at a specified time. The RunAt method calculates the TimeSpan between the current time and a time specified on the command line (in RFC1123 format, which updates RFC822) and configures a Timer object to fire once after that period of time.

using System;

using System.Threading; using System.Globalization;

namespace Apress.VisualCSharpRecipes.Chapter04

{

class Recipe04_04

{

public static void RunAt(DateTime execTime)

{

//Calculate the difference between the specified execution

//time and the current time.

TimeSpan waitTime = execTime - DateTime.Now;

if (waitTime < new TimeSpan(0)) waitTime = new TimeSpan(0);

//Create a Timer that fires once at the specified time. Specify

//an interval of -1 to stop the timer executing the method

//repeatedly. Use an anonymous method for the timer expiry handler. new Timer(delegate(object s)

{

Console.WriteLine("{0} : {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), s);

}

, null, waitTime, new TimeSpan(-1));

}

public static void Main(string[] args)

{

DateTime execTime;

// Ensure there is an execution time specified on the command line. if (args.Length > 0)

{

//Convert the string to a DateTime. Support only the RFC1123

//DateTime pattern.

try

{

execTime = DateTime.ParseExact(args[0],"r", null);

Console.WriteLine("Current time

: " +

DateTime.Now.ToString("r"));

 

Console.WriteLine("Execution time : " + execTime.ToString("r"));

RunAt(execTime);

}

catch (FormatException)

{

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

111

Console.WriteLine("Execution time must be of the format:\n\t"+ CultureInfo.CurrentCulture.DateTimeFormat.RFC1123Pattern);

}

// Wait to continue. Console.WriteLine("Waiting for Timer.");

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

}

else

{

Console.WriteLine("Specify the time you want the method to" + " execute using the format :\n\t " + CultureInfo.CurrentCulture.DateTimeFormat.RFC1123Pattern);

}

}

}

}

4-5. Execute a Method by Signaling a WaitHandle Object

Problem

You need to execute one or more methods automatically when an object derived from

System.Threading.WaitHandle is signaled.

Solution

Declare a method containing the code you want to execute. The method’s signature must match that defined by the System.Threading.WaitOrTimerCallback delegate. Using the static ThreadPool. RegisterWaitForSingleObject method, register the method to execute and the WaitHandle object that will trigger execution when signaled.

How It Works

You can use classes derived from the WaitHandle class to trigger the execution of a method. Using the

RegisterWaitForSingleObject method of the ThreadPool class, you can register a WaitOrTimerCallback delegate instance for execution by a thread-pool thread when a specified WaitHandle-derived object enters a signaled state. You can configure the thread pool to execute the method only once or to automatically reregister the method for execution each time the WaitHandle is signaled. If the WaitHandle is already signaled when you call RegisterWaitForSingleObject, the method will execute immediately. The Unregister method of the System.Threading.RegisteredWaitHandle object returned by the RegisterWaitForSingleObject method is used to cancel a registered wait operation.

The class most commonly used as a trigger is AutoResetEvent, which automatically returns to an unsignaled state after it is signaled. However, you can also use the ManualResetEvent, Mutex, and Semaphore classes, which require you to change the signaled state manually.

112 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

The Code

The following example demonstrates how to use an AutoResetEvent to trigger the execution of a method named EventHandler. (The AutoResetEvent class is discussed further in recipe 4-8.)

using System;

using System.Threading;

namespace Apress.VisualCSharpRecipes.Chapter04

{

class Recipe04_05

{

//A method that is executed when the AutoResetEvent is signaled

//or the wait operation times out.

private static void EventHandler(object state, bool timedout)

{

//Display appropriate message to the console based on whether

//the wait timed out or the AutoResetEvent was signaled.

if (timedout)

{

Console.WriteLine("{0} : Wait timed out.", DateTime.Now.ToString("HH:mm:ss.ffff"));

}

else

{

Console.WriteLine("{0} : {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), state);

}

}

public static void Main()

{

//Create the new AutoResetEvent in an unsignaled state. AutoResetEvent autoEvent = new AutoResetEvent(false);

//Create the state object that is passed to the event handler

//method when it is triggered. In this case, a message to display. string state = "AutoResetEvent signaled.";

//Register the EventHandler method to wait for the AutoResetEvent to

//be signaled. Set a time-out of 3 seconds, and configure the wait

//operation to reset after activation (last argument). RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject(

autoEvent, EventHandler, state, 3000, false);

Console.WriteLine("Press ENTER to signal the AutoResetEvent" + " or enter \"Cancel\" to unregister the wait operation.");

while (Console.ReadLine().ToUpper() != "CANCEL")

{

//If "Cancel" has not been entered into the console, signal

//the AutoResetEvent, which will cause the EventHandler

//method to execute. The AutoResetEvent will automatically

//revert to an unsignaled state.

autoEvent.Set();

}

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

113

//Unregister the wait operation. Console.WriteLine("Unregistering wait operation."); handle.Unregister(null);

//Wait to continue.

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

}

}

}

4-6. Execute a Method Using a New Thread

Problem

You need to execute code in its own thread, and you want complete control over the thread’s state and operation.

Solution

Declare a method containing the code you want to execute. The method’s signature must match that defined by the System.Threading.ThreadStart or System.Threading.ParameterizedThreadStart delegate. Create a new System.Threading.Thread object, and pass the method as an argument to its constructor. Call the Thread.Start method to start the execution of your method.

How It Works

For maximum control and flexibility when creating multithreaded applications, you need to take

a direct role in creating and managing threads. This is the most complex approach to multithreaded programming, but it is the only way to overcome the restrictions and limitations inherent in the approaches using thread-pool threads, as discussed in the preceding recipes. The Thread class provides the mechanism through which you create and control threads. To create and start a new thread, follow this process:

1.Define a method that matches the ThreadStart or ParameterizedThreadStart delegate. The ThreadStart delegate takes no arguments and returns void. This means you cannot easily pass data to your new thread. The ParameterizedThreadStart delegate also returns void but takes a single object as an argument, allowing you to pass data to the method you want to run. (The ParameterizedThreadStart delegate is a welcome addition to .NET 2.0.) The method you want to execute can be static or an instance method.

2.Create a new Thread object, and pass your method as an argument to the Thread constructor. The new thread has an initial state of Unstarted (a member of the System.Threading.ThreadState enumeration) and is a foreground thread by default. If you want to configure it to be a background thread, you need to set its IsBackground property to true.

3.Call Start on the Thread object, which changes its state to ThreadState.Running and begins execution of your method. If you need to pass data to your method, include it as an argument to the Start call. If you call Start more than once, it will throw a System.Threading. ThreadStateException.

114 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

The Code

The following code demonstrates how to execute a method in a new thread and shows you how to pass data to the new thread:

using System;

using System.Threading;

namespace Apress.VisualCSharpRecipes.Chapter04

{

class Recipe04_06

{

//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);

}

// A private class used to pass initialization data to a new thread. private class ThreadStartData

{

public ThreadStartData(int iterations, string message, int delay)

{

this.iterations = iterations; this.message = message; this.delay = delay;

}

//Member variables hold initialization data for a new thread. private readonly int iterations;

private readonly string message; private readonly int delay;

//Properties provide read-only access to initialization data. public int Iterations { get { return iterations; } }

public string Message { get { return message; } } public int Delay { get { return delay; } }

}

//Declare the method that will be executed in its own thread. The

//method displays a message to the console a specified number of

//times, sleeping between each message for a specified duration. private static void DisplayMessage(object config)

{

ThreadStartData data = config as ThreadStartData;

if (data != null)

{

for (int count = 0; count < data.Iterations; count++)

{

TraceMsg(data.Message);

// Sleep for the specified period.

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

115

Thread.Sleep(data.Delay);

}

}

else

{

TraceMsg("Invalid thread configuration.");

}

}

public static void Main()

{

//Create a new Thread object specifying DisplayMessage

//as the method it will execute.

Thread thread = new Thread(DisplayMessage);

// Create a new ThreadStartData object to configure the thread. ThreadStartData config =

new ThreadStartData(5, "A thread example.", 500);

TraceMsg("Starting new thread.");

//Start the new thread and pass the ThreadStartData object

//containing the initialization data. thread.Start(config);

//Continue with other processing.

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

{

TraceMsg("Main thread continuing processing..."); Thread.Sleep(200);

}

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

}

}

}

4-7. Synchronize the Execution of Multiple

Threads Using a Monitor

Problem

You need to coordinate the activities of multiple threads 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

Identify an appropriate object to use as a mechanism to control access to the shared resource/data. Use the static method Monitor.Enter to acquire a lock on the object, and use the static method Monitor.Exit to release the lock so another thread may acquire it.

116 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

How It Works

The greatest challenge in writing a multithreaded application is ensuring that the threads work in concert. This is commonly referred to as thread synchronization and includes the following:

Ensuring threads access shared objects and data correctly so that they do not cause corruption

Ensuring threads execute only when they are meant to and cause minimum overhead when they are idle

The most commonly used synchronization mechanism is the System.Threading.Monitor class. The Monitor class allows a single thread to obtain an exclusive lock on an object by calling the static method Monitor.Enter. By acquiring an exclusive lock prior to accessing a shared resource or data, you ensure that only one thread can access the resource concurrently. Once the thread has finished with the resource, release the lock to allow another thread to access it. A block of code that enforces this behavior is often referred to as a critical section.

Note Monitors are managed-code synchronization mechanisms that do not rely on any specific operating system primitives. This ensures your code is portable should you want to run it on a non-Windows platform. This is in contrast to the synchronization mechanisms discussed in recipes 4-8, 4-9, and 4-10, which rely on Win32 operating system–based synchronization objects.

You can use any object to act as the lock; it is common to use the keyword this to obtain a lock on the current object, but it is better to use a separate object dedicated to the purpose of synchronization. The key point is that all threads attempting to access a shared resource must try to acquire the same lock. Other threads that attempt to acquire a lock using Monitor.Enter on the same object will block (enter a WaitSleepJoin state) and are added to the lock’s ready queue until the thread that owns the lock releases it by calling the static method Monitor.Exit. When the owning thread calls Exit, one of the threads from the ready queue acquires the lock. If the owner of a lock does not release it by calling Exit, all other threads will block indefinitely. Therefore, it is important to place the Exit call within a finally block to ensure that it is called even if an exception occurs. To ensure threads do not wait indefinitely, you can specify a time-out value when you call Monitor.Enter.

Tip Because Monitor is used so frequently in multithreaded applications, C# provides language-level support through the lock statement, which the compiler translates to the use of the Monitor class. A block of code encapsulated in a lock statement is equivalent to calling Monitor.Enter when entering the block and Monitor. Exit when exiting the block. In addition, the compiler automatically places the Monitor.Exit call in a finally block to ensure that the lock is released if an exception is thrown.

Using Monitor.Enter and Monitor.Exit is often all you will need to correctly synchronize access to a shared resource in a multithreaded application. However, when you are trying to coordinate the activation of a pool of threads to handle work items from a shared queue, Monitor.Enter and Monitor. Exit will not be sufficient. In this situation, you want a potentially large number of threads to wait efficiently until a work item becomes available without putting unnecessary load on the central processing unit (CPU). This is where you need the fine-grained synchronization control provided by the Monitor.Wait, Monitor.Pulse, and Monitor.PulseAll methods.

The thread that currently owns the lock can call Monitor.Wait, which will release the lock and place the calling thread on the lock’s wait queue. Threads in a wait queue also have a state of WaitSleepJoin and will continue to block until a thread that owns the lock calls either the Monitor.Pulse method or the Monitor.PulseAll method. Monitor.Pulse moves one of the waiting threads from 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

117

wait queue to the ready queue, and Monitor.PulseAll moves all threads. Once a thread has moved from the wait queue to the ready queue, it can acquire the lock the next time the lock is released. It is important to understand that threads on a lock’s wait queue will not acquire a released lock; they will wait indefinitely until you call Monitor.Pulse or Monitor.PulseAll to move them to the ready queue.

So, in practice, when your pool threads are inactive, they sit on the wait queue. As a new work item arrives, a dispatcher obtains the lock and calls Monitor.Pulse, moving one worker thread to the ready queue where it will obtain the lock as soon as the dispatcher releases it. The worker thread takes the work item, releases the lock, and processes the work item. Once the worker thread has finished with the work item, it again obtains the lock in order to take the next work item, but if there is no work item to process, the thread calls Monitor.Wait and goes back to the wait queue.

The Code

The following example demonstrates how to synchronize access to a shared resource (the console) and the activation of waiting threads using the Monitor.Wait, Monitor.Pulse, and Monitor.PulseAll methods. The example starts three worker threads that take work items from a queue and processes them. When the user presses Enter the first two times, work items (strings in the example) are added to the work queue, and Monitor.Pulse is called to release one waiting thread for each work item. The third time the user presses Enter, Monitor.PulseAll is called, releasing all waiting threads and allowing them to terminate.

using System;

using System.Threading;

using System.Collections.Generic;

namespace Apress.VisualCSharpRecipes.Chapter04

{

class Recipe04_07

{

//Declare an object for synchronization of access to the console.

//A static object is used because you are using it in static methods. private static object consoleGate = new Object();

//Declare a Queue to represent the work queue.

private static Queue<string> workQueue = new Queue<string>();

//Declare a flag to indicate to activated threads that they should

//terminate and not process more work items.

private static bool processWorkItems = true;

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

//console along with details of the current thread.

private static void TraceMsg(string msg)

{

lock (consoleGate)

{

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

}

}

// Declare the method that will be executed by each thread to process

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