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

Visual CSharp 2005 Recipes (2006) [eng]

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

98 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

// Properties to retrieve configuration settings. public int Iterations { get { return iterations; } } public string Message { get { return message; } }

}

//A method that conforms to the System.Threading.WaitCallback delegate

//signature. Displays a message to the console.

public static void DisplayMessage(object state)

{

//Safely cast the state argument to a MessageInfo object. MessageInfo config = state as MessageInfo;

//If the config argument is null, no arguments were passed to

//the ThreadPool.QueueUserWorkItem method; use default values. if (config == null)

{

//Display a fixed message to the console three times.

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

{

Console.WriteLine("A thread pool example.");

//Sleep for the purpose of demonstration. Avoid sleeping

//on thread-pool threads in real applications. Thread.Sleep(1000);

}

}

else

{

// Display the specified message the specified number of times. for (int count = 0; count < config.Iterations; count++)

{

Console.WriteLine(config.Message);

//Sleep for the purpose of demonstration. Avoid sleeping

//on thread-pool threads in real applications. Thread.Sleep(1000);

}

}

}

public static void Main()

{

//Execute DisplayMessage using the thread pool and no arguments. ThreadPool.QueueUserWorkItem(DisplayMessage);

//Create a MessageInfo object to pass to the DisplayMessage method. MessageInfo info = new MessageInfo(5,

"A thread pool example with arguments.");

//Execute DisplayMessage using the thread pool and providing an

//argument.

ThreadPool.QueueUserWorkItem(DisplayMessage, info);

// Wait to continue.

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

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

99

}

}

}

Notes

Using the runtime’s thread pool simplifies multithreaded programming dramatically; however, be aware that the implementation is a simple, general-purpose thread pool. Before deciding to use the thread pool, consider the following points:

Each process has one thread pool, which supports by default a maximum of 25 concurrent threads per processor. You can change the maximum number of threads using the method ThreadPool.SetMaxThreads, but some runtime hosts (Internet Information Services [IIS] and SQL Server, for example) will limit the maximum number of threads and may not allow the default value to be changed at all.

As well as allowing you to use the thread pool to execute code directly, the runtime uses the thread pool for other purposes internally. This includes the asynchronous execution of methods (see recipe 4-2), execution of timer events (see recipes 4-3 and 4-4), and execution of wait-based methods (see recipe 4-5). All of these uses can lead to heavy contention for the thread-pool threads, meaning that the work queue can become very long. Although the work queue’s maximum length is limited only by the amount of memory available to the runtime’s process, an excessively long queue will result in long delays before queued work items are executed. The ThreadPool.GetAvailableThreads method returns the number of threads currently available in the thread pool. This can be useful in determining whether your application is placing too much load on the thread pool, indicating that you should increase the number of available threads using the ThreadPool.SetMaxThreads method.

Where possible, avoid using the thread pool to execute long-running processes. The limited number of threads in the thread pool means that a handful of threads tied up with long-running processes can significantly affect the overall performance of the thread pool. Specifically, you should avoid putting thread-pool threads to sleep for any length of time.

Thread-pool threads are background threads. You can configure threads as either foreground threads or background threads. Foreground and background threads are identical except that a background thread will not keep an application process alive. Therefore, your application will terminate automatically when the last foreground thread of your application terminates.

You have no control over the scheduling of thread-pool threads, and you cannot prioritize work items. The thread pool handles each work item in the sequence in which you add it to the work queue.

Once a work item is queued, it cannot be canceled or stopped.

Do not try to use thread-pool threads to update or manipulate Windows Forms controls, because they can be updated only by the thread that created them.

4-2. Execute a Method Asynchronously

Problem

You need to start execution of a method and continue with other tasks while the method runs on a separate thread. After the method completes, you need to retrieve the method’s return value.

100 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

Solution

Declare a delegate with the same signature as the method you want to execute. Create an instance of the delegate that references the method. Call the BeginInvoke method of the delegate instance to start executing your method. Use the EndInvoke method to determine the method’s status as well as obtain the method’s return value if complete.

How It Works

Typically, when you invoke a method, you do so synchronously, meaning that the calling code blocks until the method is complete. Most of the time, this is the expected, desired behavior because your code requires the operation to complete before it can continue. However, sometimes it is useful to execute a method asynchronously, meaning that you start the method in a separate thread and then continue with other operations.

The .NET Framework implements an asynchronous execution pattern that allows you to call any method asynchronously using a delegate. When you declare and compile a delegate, the compiler automatically generates two methods that support asynchronous execution: BeginInvoke and EndInvoke. When you call BeginInvoke on a delegate instance, the method referenced by the delegate is queued for asynchronous execution. Control returns to the caller immediately, and the referenced method executes in the context of the first available thread-pool thread.

The signature of the BeginInvoke method includes the same arguments as those specified by the delegate signature, followed by two additional arguments to support asynchronous completion. These additional arguments are as follows:

A System.AsyncCallback delegate instance that references a method that the runtime will call when the asynchronous method completes. The method will be executed by a thread-pool thread. Passing null means no method is called and means you must use another mechanism (discussed later in this recipe) to determine when the asynchronous method is complete.

A reference to an object that the runtime associates with the asynchronous operation for you. The asynchronous method does not use or have access to this object, but it is available to your code when the method completes, allowing you to associate useful state information with an asynchronous operation. For example, this object allows you to map results against initiated operations in situations where you initiate many asynchronous operations that use a common callback method to perform completion.

The EndInvoke method allows you to retrieve the return value of a method that was executed asynchronously, but you must first determine when it has finished. If your asynchronous method threw an exception, it will be rethrown so that you can handle it when you call EndInvoke. Here are the four techniques for determining whether an asynchronous method has finished:

Blocking stops the execution of the current thread until the asynchronous method completes execution. In effect, this is much the same as synchronous execution. However, you have the flexibility to decide exactly when your code enters the blocked state, giving you the opportunity to perform some additional processing before blocking.

Polling involves repeatedly testing the state of an asynchronous method to determine whether it is complete. This is a simple technique and is not particularly efficient from a processing perspective. You should avoid tight loops that consume processor time; it is best to put the polling thread to sleep for a period using Thread.Sleep between completion tests. Because polling involves maintaining a loop, the actions of the waiting thread are limited, but you can easily update some kind of progress indicator.

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

101

Waiting depends on the AsyncWaitHandle property of the IAsyncResult returned by BeginInvoke. This object derives from the System.Threading.WaitHandle class signals when the asynchronous method completes. Waiting is a more efficient version of polling and in addition allows you to wait for multiple asynchronous methods to complete. You can also specify time-out values to allow your waiting thread to notify a failure if the asynchronous method takes too long or if you want to periodically update a status indicator.

A callback is a method that the runtime calls when an asynchronous operation completes. The calling code does not have to take any steps to determine when the asynchronous method is complete and is free to continue with other processing. Callbacks provide the greatest flexibility but also introduce the greatest complexity, especially if you have many asynchronous operations active concurrently that all use the same callback. In such cases, you must use appropriate state objects as the last parameter of BeginInvoke to match the completed methods against those you initiated.

Caution Even if you do not want to handle the return value of your asynchronous method, you should call EndInvoke; otherwise, you risk leaking memory each time you initiate an asynchronous call using BeginInvoke.

The Code

The following code demonstrates how to use the asynchronous execution pattern. It uses a delegate named AsyncExampleDelegate to execute a method named LongRunningMethod asynchronously. LongRunningMethod simulates a long-running method using a configurable delay (produced using Thread.Sleep). The example contains the following five methods that demonstrate the various approaches to handling asynchronous method completion:

The BlockingExample method executes LongRunningMethod asynchronously and continues with a limited set of processing. Once this processing is complete, BlockingExample blocks until LongRunningMethod completes. To block, BlockingExample calls the EndInvoke method of the AsyncExampleDelegate delegate instance. If LongRunningMethod has already finished,

EndInvoke returns immediately; otherwise, BlockingExample blocks until LongRunningMethod completes.

The PollingExample method executes LongRunningMethod asynchronously and then enters a polling loop until LongRunningMethod completes. PollingExample tests the IsCompleted property of the IAsyncResult instance returned by BeginInvoke to determine whether

LongRunningMethod is complete; otherwise, PollingExample calls Thread.Sleep.

The WaitingExample method executes LongRunningMethod asynchronously and then waits until LongRunningMethod completes. WaitingExample uses the AsyncWaitHandle property of the

IAsyncResult instance returned by BeginInvoke to obtain a WaitHandle and then calls its WaitOne method. Using a time-out allows WaitingExample to break out of waiting in order to perform other processing or to fail completely if the asynchronous method is taking too long.

The WaitAllExample method executes LongRunningMethod asynchronously multiple times and then uses an array of WaitHandle objects to wait efficiently until all the methods are complete.

The CallbackExample method executes LongRunningMethod asynchronously and passes an AsyncCallback delegate instance (that references the CallbackHandler method) to the BeginInvoke method. The referenced CallbackHandler method is called automatically when the asynchronous LongRunningMethod completes, leaving the CallbackExample method free to continue processing.

102 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

Note For the purpose of demonstrating the various synchronization techniques, the example performs several tasks that should be avoided when using the thread pool, including putting thread-pool threads to sleep and calling long-running methods. See recipe 4-1 for more suggestions on using the thread pool appropriately.

using System;

using System.Threading; using System.Collections;

namespace Apress.VisualCSharpRecipes.Chapter04

{

class Recipe04_02

{

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

//console along with details of the current thread.

private static void TraceMsg(DateTime time, string msg)

{

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

}

//A delegate that allows you to perform asynchronous execution of

//LongRunningMethod.

public delegate DateTime AsyncExampleDelegate(int delay, string name);

// A simulated long-running method.

public static DateTime LongRunningMethod(int delay, string name)

{

TraceMsg(DateTime.Now, name + " example - thread starting.");

// Simulate time-consuming processing. Thread.Sleep(delay);

TraceMsg(DateTime.Now, name + " example - thread stopping.");

// Return the method's completion time. return DateTime.Now;

}

//This method executes LongRunningMethod asynchronously and continues

//with other processing. Once the processing is complete, the method

//blocks until LongRunningMethod completes.

public static void BlockingExample()

{

Console.WriteLine(Environment.NewLine + "*** Running Blocking Example ***");

//Invoke LongRunningMethod asynchronously. Pass null for both the

//callback delegate and the asynchronous state object. AsyncExampleDelegate longRunningMethod = LongRunningMethod;

IAsyncResult asyncResult = longRunningMethod.BeginInvoke(2000, "Blocking", null, null);

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

103

// Perform other processing until ready to block. for (int count = 0; count < 3; count++)

{

TraceMsg(DateTime.Now,

"Continue processing until ready to block...");

Thread.Sleep(200);

}

//Block until the asynchronous method completes. TraceMsg(DateTime.Now,

"Blocking until method is complete...");

//Obtain the completion data for the asynchronous method. DateTime completion = DateTime.MinValue;

try

{

completion = longRunningMethod.EndInvoke(asyncResult);

}

catch

{

//Catch and handle those exceptions you would if calling

//LongRunningMethod directly.

}

// Display completion information TraceMsg(completion,"Blocking example complete.");

}

//This method executes LongRunningMethod asynchronously and then

//enters a polling loop until LongRunningMethod completes. public static void PollingExample()

{

Console.WriteLine(Environment.NewLine + "*** Running Polling Example ***");

//Invoke LongRunningMethod asynchronously. Pass null for both the

//callback delegate and the asynchronous state object. AsyncExampleDelegate longRunningMethod = LongRunningMethod;

IAsyncResult asyncResult = longRunningMethod.BeginInvoke(2000, "Polling", null, null);

//Poll the asynchronous method to test for completion. If not

//complete, sleep for 300ms before polling again. TraceMsg(DateTime.Now, "Poll repeatedly until method is complete.");

while (!asyncResult.IsCompleted)

{

TraceMsg(DateTime.Now, "Polling..."); Thread.Sleep(300);

}

// Obtain the completion data for the asynchronous method. DateTime completion = DateTime.MinValue;

104 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

try

{

completion = longRunningMethod.EndInvoke(asyncResult);

}

catch

{

//Catch and handle those exceptions you would if calling

//LongRunningMethod directly.

}

// Display completion information. TraceMsg(completion, "Polling example complete.");

}

//This method executes LongRunningMethod asynchronously and then

//uses a WaitHandle to wait efficiently until LongRunningMethod

//completes. Use of a time-out allows the method to break out of

//waiting in order to update the user interface or fail if the

//asynchronous method is taking too long.

public static void WaitingExample()

{

Console.WriteLine(Environment.NewLine + "*** Running Waiting Example ***");

//Invoke LongRunningMethod asynchronously. Pass null for both the

//callback delegate and the asynchronous state object. AsyncExampleDelegate longRunningMethod = LongRunningMethod;

IAsyncResult asyncResult = longRunningMethod.BeginInvoke(2000, "Waiting", null, null);

//Wait for the asynchronous method to complete. Time-out after

//300ms and display status to the console before continuing to

//wait.

TraceMsg(DateTime.Now, "Waiting until method is complete...");

while (!asyncResult.AsyncWaitHandle.WaitOne(300, false))

{

TraceMsg(DateTime.Now, "Wait timeout...");

}

// Obtain the completion data for the asynchronous method. DateTime completion = DateTime.MinValue;

try

{

completion = longRunningMethod.EndInvoke(asyncResult);

}

catch

{

//Catch and handle those exceptions you would if calling

//LongRunningMethod directly.

}

// Display completion information. TraceMsg(completion, "Waiting example complete.");

}

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

105

//This method executes LongRunningMethod asynchronously multiple

//times and then uses an array of WaitHandle objects to wait

//efficiently until all of the methods are complete. Use of

//a time-out allows the method to break out of waiting in order

//to update the user interface or fail if the asynchronous

//method is taking too long.

public static void WaitAllExample()

{

Console.WriteLine(Environment.NewLine + "*** Running WaitAll Example ***");

//An ArrayList to hold the IAsyncResult instances for each of the

//asynchronous methods started.

ArrayList asyncResults = new ArrayList(3);

//Invoke three LongRunningMethods asynchronously. Pass null for

//both the callback delegate and the asynchronous state object.

//Add the IAsyncResult instance for each method to the ArrayList. AsyncExampleDelegate longRunningMethod = LongRunningMethod;

asyncResults.Add(longRunningMethod.BeginInvoke(3000, "WaitAll 1", null, null));

asyncResults.Add(longRunningMethod.BeginInvoke(2500, "WaitAll 2", null, null));

asyncResults.Add(longRunningMethod.BeginInvoke(1500, "WaitAll 3", null, null));

//Create an array of WaitHandle objects that will be used to wait

//for the completion of all the asynchronous methods. WaitHandle[] waitHandles = new WaitHandle[3];

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

{

waitHandles[count] = ((IAsyncResult)asyncResults[count]).AsyncWaitHandle;

}

//Wait for all three asynchronous method to complete. Time out

//after 300ms and display status to the console before continuing

//to wait.

TraceMsg(DateTime.Now, "Waiting until all 3 methods are complete...");

while (!WaitHandle.WaitAll(waitHandles, 300, false))

{

TraceMsg(DateTime.Now, "WaitAll timeout...");

}

//Inspect the completion data for each method, and determine the

//time at which the final method completed.

DateTime completion = DateTime.MinValue;

foreach (IAsyncResult result in asyncResults)

{

try

{

106 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

DateTime time = longRunningMethod.EndInvoke(result); if (time > completion) completion = time;

}

catch

{

//Catch and handle those exceptions you would if calling

//LongRunningMethod directly.

}

}

// Display completion information TraceMsg(completion, "WaitAll example complete.");

}

//This method executes LongRunningMethod asynchronously and passes

//an AsyncCallback delegate instance. The referenced CallbackHandler

//method is called automatically when the asynchronous method

//completes, leaving this method free to continue processing. public static void CallbackExample()

{

Console.WriteLine(Environment.NewLine + "*** Running Callback Example ***");

//Invoke LongRunningMethod asynchronously. Pass an AsyncCallback

//delegate instance referencing the CallbackHandler method that

//will be called automatically when the asynchronous method

//completes. Pass a reference to the AsyncExampleDelegate delegate

//instance as asynchronous state; otherwise, the callback method

//has no access to the delegate instance in order to call

//EndInvoke.

AsyncExampleDelegate longRunningMethod = LongRunningMethod;

IAsyncResult asyncResult = longRunningMethod.BeginInvoke(2000, "Callback", CallbackHandler, longRunningMethod);

// Continue with other processing.

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

{

TraceMsg(DateTime.Now, "Continue processing..."); Thread.Sleep(200);

}

}

// A method to handle asynchronous completion using callbacks. public static void CallbackHandler(IAsyncResult result)

{

//Extract the reference to the AsyncExampleDelegate instance

//from the IAsyncResult instance. This allows you to obtain the

//completion data.

AsyncExampleDelegate longRunningMethod = (AsyncExampleDelegate)result.AsyncState;

// Obtain the completion data for the asynchronous method. DateTime completion = DateTime.MinValue;

Tip

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

107

try

{

completion = longRunningMethod.EndInvoke(result);

}

catch

{

//Catch and handle those exceptions you would if calling

//LongRunningMethod directly.

}

// Display completion information. TraceMsg(completion, "Callback example complete.");

}

public static void Main()

{

//Demonstrate the various approaches to asynchronous method completion. BlockingExample();

PollingExample();

WaitingExample();

WaitAllExample();

CallbackExample();

//Wait to continue.

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

}

}

}

4-3. Execute a Method Periodically

Problem

You need to execute a method in a separate thread periodically.

Solution

Declare a method containing the code you want to execute periodically. The method’s signature must match that defined by the System.Threading.TimerCallback delegate; in other words, 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. The runtime will wait until the timer expires and then call your method using a thread from the thread pool.

If you are implementing a timer in a Windows Forms application, you should consider using the System.Windows.Forms.Timer, which also provides additional support in Visual Studio that allows you to drag the timer from your Toolbox onto your application. For server-based applications where you want to signal multiple listeners each time the timer fires, consider using the System.Timers.Timer class, which notifies listeners using events.

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