1. Trang chủ
  2. » Công Nghệ Thông Tin

Visual C# 2010 Recipes solution_1 pptx

95 568 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 95
Dung lượng 1,87 MB

Nội dung

CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 165 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. 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); } CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 166 // 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. The Code The following code demonstrates how to use a Timer object to execute a method at a specified time: using System; using System.Threading; using System.Globalization; CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 167 namespace Apress.VisualCSharpRecipes.Chapter04 { class Recipe04_04 { public static void Main(string[] args) { // Create a 30-second timespan. TimeSpan waitTime = new TimeSpan(0, 0, 30); // 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 anonymouse method for the timer expiry handler. new Timer(delegate(object s) { Console.WriteLine("Timer fired at {0}", DateTime.Now.ToString("HH:mm:ss.ffff")); } , null, waitTime, new TimeSpan(-1)); Console.WriteLine("Waiting for timer. Press Enter to terminate."); Console.ReadLine(); } } } 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 CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 168 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. 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 timeout of 10 seconds, and configure the wait // operation to reset after activation (last argument). CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 169 RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject( autoEvent, EventHandler, state, 10000, 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(); } // 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 CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 170 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. 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; CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 171 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. 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); // Make this a foreground thread - this is the // default - call used for example purposes. thread.IsBackground = false; // Create a new ThreadStartData object to configure the thread. ThreadStartData config = new ThreadStartData(5, "A thread example.", 500); TraceMsg("Starting new thread."); CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 172 // 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 within a single process to ensure the efficient use of shared resources or to ensure that several threads are not updating the same shared resource at the same time. (See recipe 4-9 for details of coordination between processes.) 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. 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 that threads access shared objects and data correctly so that they do not cause corruption • Ensuring that threads execute only when they are meant to and cause minimum overhead when they are idle CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 173 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 shared 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 that 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 will be 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 that threads do not wait indefinitely, you can specify a timeout 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 wait queue to the CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 174 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) { [...]... toggle the event between a signaled and a unsignaled state This example uses the Thread.Join instance method, which we describe in recipe 4-12 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... 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 { 182 CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION // Boolean to signal that the second thread should terminate static bool terminate = false; // A utility... 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; 185 CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION // A utility... demonstrate Interlocked in the context of a multithreaded program and is provided only to clarify the syntax and effect of the various methods using System; using System.Threading; namespace Apress.VisualCSharpRecipes.Chapter04 { class Recipe04_11 { public static void Main() { int firstInt = 2500; int secondInt = 8000; 188 CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION Console.WriteLine("firstInt... example will display a message to the console The example then calls Join again without a timeout and blocks until the second thread terminates using System; using System.Threading; namespace Apress.VisualCSharpRecipes.Chapter04 { class Recipe04_12 { private static void DisplayMessage() { // Display a message to the console five times for (int count = 0; count < 5; count++) { Console.WriteLine("{0} : DisplayMessage... that continues to display messages to the console until you press Enter, at which point the thread is terminated by a call to Thread.Abort: using System; using System.Threading; namespace Apress.VisualCSharpRecipes.Chapter04 { class Recipe04_13 { private static void DisplayMessage() { try { while (true) { // Display a message to the console Console.WriteLine("{0} : DisplayMessage thread active", DateTime.Now.ToString("HH:mm:ss.ffff"));... method returns true if the process ends before the timeout and returns false otherwise 197 CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION using System; using System.Diagnostics; namespace Apress.VisualCSharpRecipes.Chapter04 { class Recipe04_15 { public static void Main() { // Create a ProcessStartInfo object and configure it with the // information required to run the new process ProcessStartInfo startInfo... Notepad process to terminate; you can force CloseMainWindow to return false by leaving the File Open dialog box open using System; using System.Threading; using System.Diagnostics; namespace Apress.VisualCSharpRecipes.Chapter04 { class Recipe04_16 { public static void Main() { // Create a new Process and run notepad.exe using (Process process = Process.Start("notepad.exe",@"c:\SomeFile.txt")) { // Wait... the Abort method of the Thread object you want to terminate How It Works It is better to write your code so that you can signal to a thread that it should shut down and allow it to terminate naturally Recipes 4-7, 4-8, and 4-9 demonstrate this technique (using a Boolean flag) However, sometimes you will want a more direct method of terminating an active thread Calling Abort on an active Thread object . CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 16 5 using System; using System.Threading; namespace Apress.VisualCSharpRecipes.Chapter04 { class Recipe04_03 { public static. System.Globalization; CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION 16 7 namespace Apress.VisualCSharpRecipes.Chapter04 { class Recipe04_04 { public static void Main(string[]. Thread.Join instance method, which we describe in recipe 4 -12 . using System; using System.Threading; namespace Apress.VisualCSharpRecipes.Chapter04 { class Recipe04_08 { // Boolean

Ngày đăng: 20/06/2014, 08:20