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

ebook Addison -Essential CSharp 4.0_8 doc

98 366 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 98
Dung lượng 1,75 MB

Nội dung

ptg Running LINQ Queries in Parallel 737 Listing 18.17: Canceling a Parallel Loop using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; public class Program { public static List<string> ParallelEncrypt( List<string> data, CancellationToken cancellationToken) { (item) => Encrypt(item)).ToList(); } public static void Main() { List<string> data = Utility.GetData(1000000).ToList(); Console.WriteLine("Push ENTER to exit."); Task task = Task.Factory.StartNew(() => { data = ParallelEncrypt(data, cts.Token); } , cts.Token); // Wait for the user's input Console.Read(); Console.Write(stars); // } return data.AsParallel(). WithCancellation( cancellationToken).Select( CancellationTokenSource cts = new CancellationTokenSource(); cts.Cancel(); try{task.Wait();} catch (AggregateException){} } OUTPUT 18.8: ERROR: The operation was canceled. From the Library of Wow! eBook ptg Chapter 18: Multithreading738 As with a parallel loop, canceling a PLINQ query requires a Cancella- tionToken, which is available on a CancellationTokenSource.Token prop- erty. However, rather than overloading every PLINQ query to support the cancellation token, the ParallelQuery<T> object returned by IEnumera- ble’s AsParallel() method includes a WithCancellation() extension method that simply takes a CancellationToken. As a result, calling Can- cel() on the CancellationTokenSource object will request the parallel query to cancel—because it checks the IsCancellationRequested property on the CancellationToken. As mentioned, canceling a PLINQ query will throw an exception in place of returning the complete result. Therefore, all canceled PLINQ que- ries will need to be wrapped by try{…}/catch(OperationCanceledExcep- tion){…} blocks to avoid an unhandled exception. Alternatively, as shown in Listing 18.17, pass the CancellationToken to both ParallelEncrypt() and as a second parameter on StartNew(). This will cause task.Wait() to throw an AggregateException whose InnerException property will be set to a TaskCanceledException. Multithreading before .NET Framework 4 TPL is a fantastic library covering a multitude of multithreading patterns with extensibility points to handle even more. However, there is one sig- nificant drawback to TPL: It is available only for the .NET Framework 4 or for use with the Rx library in .NET 3.5. In this section, we cover multi- threading technology before TPL. Asynchronous Operations with System.Threading.Thread Listing 18.18 (with Output 18.9) provides an example. Like TPL, there is a fundamental type, System.Threading.Thread, which is used to control an asynchronous operation. Like System.Threading.Tasks.Task in TPL, Thread includes a Start method and a wait equivalent, Join(). Listing 18.18: Starting a Method Using System.Threading.Thread using System; public class RunningASeparateThread using System.Threading; From the Library of Wow! eBook ptg Multithreading before .NET Framework 4 739 { public const int Repetitions = 1000; public static void Main() { for (int count = 0; count < Repetitions; count++) { Console.Write('-'); } } public static void DoWork() { for (int count = 0; count < Repetitions; count++) { Console.Write('.'); } } } ThreadStart threadStart = DoWork; Thread thread = new Thread(threadStart); thread.Start(); thread.Join(); OUTPUT 18.9: From the Library of Wow! eBook ptg Chapter 18: Multithreading740 Like the output of Listing 18.9, which used TPL, Listing 18.18’s code (see Output 18.9) intersperses . and – in the output. The code that is to exe- cute in a new thread appears in the DoWork() method. The DoWork() method outputs a . during each iteration within a loop. Besides the fact that it contains code for starting another thread, the Main() method is vir- tually identical in structure to DoWork(), except that it displays The resultant output is due to a series of dashes until the thread context switches, at which time the program displays periods until the next thread switch, and so on. 2 In order for code to run under the context of a different thread, you need a delegate of type System.Threading.ThreadStart or System. Threading.ParameterizedThreadStart (the latter allows for a single parameter of type object), identifying the code to execute. Given a Thread instance created using the thread-start delegate constructor, you can start the thread executing with a call to thread.Start(). (Listing 18.18 shows the ThreadStart explicitly to identify the delegate type. In general, DoWork could be passed directly to the thread constructor using C# 2.0’s delegate inference.) Starting the thread simply involves a call to Thread.Start(). As soon as the DoWork() method begins execution, the call to Thread. Start() returns and executes the for loop in the Main() method. The threads are now independent and neither waits for the other. The output from Listing 18.18 and Listing 18.19 will intermingle the output of each thread, instead of creating a series of . followed by Thread Management Threads include a number of methods and properties for managing their execution. • Join(): Once threads are started, you can cause a “wait for comple- tion” with a call to thread.Join(). The calling thread will wait until the thread instance terminates. The Join() method is over- loaded to take either an int or a TimeSpan to support a maximum time to wait for thread completion before continuing execution. 2. As mentioned earlier, it is possible to increase the chances of a thread context switch by using Start /low /b <program.exe> to execute the program. From the Library of Wow! eBook ptg Multithreading before .NET Framework 4 741 • IsBackground: Another thread configuration option is the thread.IsBackGround property. By default, a thread is a foreground thread, meaning the process will not terminate until the thread com- pletes. In contrast, setting the IsBackground property to true will allow process execution to terminate prior to a thread’s completion. • Priority: When using the Join() method, you can increase or decrease the thread’s priority by setting the Priority to a new ThreadPriority enum value (Lowest, BelowNormal, Normal, Above- Normal, or Highest). • ThreadState: A thread’s state is accessible through the ThreadState property, a more precise reflection of the Boolean IsAlive property. The ThreadState enum flag values are Aborted, AbortRequested, Background, Running, Stopped, StopRequested, Suspended, Suspend- Requested, Unstarted, and WaitSleepJoin. The flag names indicate activities that may occur on a thread. Two noteworthy methods are Thread.Sleep() and Abort(). • Thread.Sleep(): Thread.Sleep() is a static method that pauses the current thread for a period. A single parameter (in milliseconds, or a TimeSpan) specifies how long the active thread waits before continu- ing execution. This enables switching to a different thread for a spe- cific period. This method is not for accurate timing. Returns can occur hundreds of milliseconds before or after the specified time. • Abort(): A thread’s Abort() method causes a ThreadAbortException to be thrown within the target thread at whatever location the thread is executing when Abort() is invoked. As already detailed, aborting a thread introduces uncertainty into the thread’s behavior and could cause data integrity and resource cleanup problems. Developers should consider the Abort() method to be a last resort. Instead, they should rely on threads running to completion and/or signaling them to escape out of whatever code is running via some with shared state. From this list of Thread members, only Join() and ThreadState have Task equivalents. For the most part, this is because there are generally preferable From the Library of Wow! eBook ptg Chapter 18: Multithreading742 equivalents or the behavior of the member is undesirable as a best practice. For example, aborting a thread may threaten data integrity or inadequate resource de-allocation, as mentioned earlier in the chapter. Therefore, given the .NET Framework 4, developers should generally avoid these members in favor of their task equivalents or alternative patterns entirely. In summary, the general priority for selecting from the asynchronous class options is Task, ThreadPool, and Thread. In other words, use TPL, but if that doesn’t fit, use ThreadPool; if that still doesn’t suffice, use Thread. One particular Thread member that is likely to crop up more frequently because there is no Task or ThreadPool equivalent is Thread.Sleep(). Although, if it doesn’t introduce too much unnecessary complexity, con- sider using a timer in place of Sleep(). Thread Pooling Regardless of the number of processors, an excess of threads negatively affects performance. To efficiently manage thread creation, TPL makes extensive use of CLR’s thread pool, System.Threading.ThreadPool. Most importantly, the thread pool dynamically determines when to use existing threads rather than creating new ones. Fortunately, the .NET 3.5 Frame- work includes a version of the System.Threading.ThreadPool, so it is available even without TPL. Accessing threads in ThreadPool is similar to explicit use of the Thread class except that the invocation is via a static method, QueueUser- WorkItem() (see Listing 18.19). Listing 18.19: Using ThreadPool Instead of Instantiating Threads Explicitly using System; using System.Threading; public class Program { public const int Repetitions = 1000; public static void Main() { for (int count = 0; count < Repetitions; count++) { ThreadPool.QueueUserWorkItem(DoWork, '.'); From the Library of Wow! eBook ptg Multithreading before .NET Framework 4 743 Console.Write('-'); } // Pause until the thread completes } { for (int count = 0; count < Repetitions; count++) { Console.Write(state); } } } The output is similar to Output 18.9, an intermingling of . and This pro- vides more-efficient execution on single- and multiprocessor computers. The efficiency is achieved by reusing threads over and over, rather than reconstructing them for every asynchronous call. Unfortunately, thread pool use is not without its pitfalls. Activities such as I/O operations and other framework methods that internally use the thread pool can consume threads as well. Consuming all threads within the pool can delay execution and, in extreme cases, cause a dead- lock. Similarly, if the asynchronous code will take a long time to execute, then it is inappropriate to consume a shared thread from the thread pool and instead favor explicit Thread instantiation (use TaskCreationOp- tions.LongRunning given TPL as mentioned earlier). Unfortunately, another disadvantage with the thread pool is that, unlike either Thread or Task, the ThreadPool API does not return a han- dle to the thread or task itself. This prevents the calling thread from con- trolling it with the thread management functions described earlier in the chapter. Just monitoring state is not available without explicitly adding a custom implementation. Assuming these deficiencies are not critical, developers should consider using the thread pool over explicit thread creation because of it increased efficiency—at least prior to .NET Frame- work 4 and TPL; the fact that TPL uses the thread pool internally indi- cates the significance of using it for the majority of multithreading scenarios. Thread.Sleep(1000); public static void DoWork(object state) From the Library of Wow! eBook ptg Chapter 18: Multithreading744 Unhandled Exceptions on the AppDomain To catch all exceptions from a thread (for which appropriate handling is known), you surround the root code block with a try/catch/finally block, just as you would for all code within Main(). However, what happens if a third-party component creates an alternate thread and throws an unhan- dled exception from that thread? Similarly, what if queued work on the thread pool throws an exception? A try/catch block in Main() will not catch an exception on an alternate thread. Furthermore, without access to any “handle” that invoked the thread (such as a Task) there is no way to catch any exceptions that it might throw. Even if there was, the code could never appropriately recover from all possible exceptions and continue exe- cuting (in fact, this is why in .NET 4.0 exceptions such as System.Stack- OverflowException, for example, will not be caught and instead will tear down the application). The general unhandled-exceptions guideline is for the program to shut down and restart in a clean state instead of behaving erratically or hanging because of an invalid state. However, instead of crashing suddenly or ignoring an unhandled exception entirely if it occurs on an alternate thread, it is often desirable to save any working data and/or log the exception for error reporting and future debugging. This requires a mechanism to register for notifications of unhandled exceptions. Registering for unhandled exceptions on the main application domain occurs via an application domain’s UnhandledException event. Listing 18.20 demonstrates that process, and Output 18.10 shows the results. Listing 18.20: Registering for Unhandled Exceptions using System; using System.Threading; public class Program { public static void Main() { try { // Register a callback to // receive notifications // of any unhandled exception. AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; From the Library of Wow! eBook ptg Unhandled Exceptions on the AppDomain 745 ThreadPool.QueueUserWorkItem( state => { throw new Exception( "Arbitrary Exception"); }); // // Wait for the unhandled exception to fire // ADVANCED: Use ManualResetEvent to avoid // timing dependent code. Thread.Sleep(10000); Console.WriteLine("Still running "); } finally { Console.WriteLine("Exiting "); } } public static void ThrowException() { throw new ApplicationException( "Arbitrary exception"); } } static void OnUnhandledException( object sender, UnhandledExceptionEventArgs eventArgs) { Exception exception = (Exception)eventArgs.ExceptionObject; Console.WriteLine("ERROR ({0}):{1} > {2}", exception.GetType().Name, exception.Message, exception.InnerException.Message); } OUTPUT 18.10: Still running Exiting ERROR (AggregateException):One or more errors occurred. > Arbitrary Exception From the Library of Wow! eBook ptg Chapter 18: Multithreading746 The UnhandledException callback will fire for all unhandled exceptions on threads within the application domain, including the main thread. This is a notification mechanism, not a mechanism to catch and process exceptions so that the application can continue. After the event, the application will exit. In fact, the unhandled exception will cause the Windows Error dialog to display (Dr. Watson). And for console applications, the exception will appear on the console. Astute readers will note that in Listing 18.20 we use ThreadPool rather than Task. This is because of the likelihood that the garbage collector will not have executed on Task before the application begins to shut down and any exceptions within the finalization will be suppressed rather than going unhandled. The likelihood of this case in most programs is generally low, but the best practice to avoid significant unhandled exceptions during application exit is to support task cancellation to cancel the task and wait for it to exit before shutting down the application. SUMMARY This chapter delved into the details surrounding the creation and manipu- lation of threads using the .NET Framework 4-introduced Task Parallel Library or TPL. This library includes new APIs for executing for and foreach loops such that iterations can potentially run in parallel. Underly- ing TPL is a new fundamental threading class, System.Threading. Tasks.Task, the basic threading unit on which all of TPL is based. It pro- vides the standard multithreaded programming and monitoring activities and keeps them relatively simple. Given that Task forms the basis for par- allel loops (Parallel.For() and Parallel.ForEach()), PLINQ, and more, it is clear that Task and its peer classes also enable a multitude of more complex threading scenarios—including unhandled exception handling and Task chaining/notifications—via Task.ContinueWith<T>. In addition, the chapter demonstrated Parallel LINQ (PLINQ) in which a single extension method, AsParallel(), transforms all further LINQ queries to run in parallel. The elegance and simplicity with which this fits into the framework is superb. From the Library of Wow! eBook [...]... multithreading problems such as deadlocks and race conditions, but the chapter never discussed how to avoid them This is the topic of the next chapter From the Library of Wow! eBook This page intentionally left blank From the Library of Wow! eBook 19 Synchronization and More Multithreading Patterns we discussed the details of multithreaded programming using the Task Parallel Library (TPL) and Parallel LINQ... a result back to _Count essentially wipes out any _Count value changes that occurred since the read of _Count on the same thread From the Library of Wow! eBook 752 Chapter 19: Synchronization and More Multithreading Patterns TABLE 19.1: Sample Pseudocode Execution Main Thread Decrement Thread Count Copy the value 0 out of _Count 0 Increment the copied value (0), resulting in 1 0 Copy the resultant... using System.Threading.Tasks; class Program { const int _Total = int.MaxValue; static long _Count = 0; public static void Main() { Task task = Task.Factory.StartNew(Decrement); From the Library of Wow! eBook Synchronization 751 // Increment for (int i = 0; i < _Total; i++) { _Count++; } task.Wait(); Console.WriteLine("Count = {0}", _Count); } static void Decrement() { // Decrement for (int i = 0; i { x++; x ; }); Console.WriteLine("Count = {0}", x); } } From the Library of Wow! eBook 754 Chapter 19: Synchronization and More Multithreading Patterns In this example, x (a local variable) is accessed within a parallel for loop and so multiple threads will modify it simultaneously,... const int _Total = int.MaxValue; static long _Count = 0; public static void Main() { Task task = Task.Factory.StartNew(Decrement); // Increment for (int i = 0; i < _Total; i++) From the Library of Wow! eBook Synchronization 755 { bool lockTaken = false; Monitor.Enter(_Sync, ref lockTaken); try { _Count++; } finally { if (lockTaken) { Monitor.Exit(_Sync); } } } task.Wait(); Console.WriteLine("Count =... Monitor.Enter(_Sync, ref lockTaken); try { _Count ; } finally { if (lockTaken) { Monitor.Exit(_Sync); } } } } } The results of Listing 19.3 appear in Output 19.2 OUTPUT 19.2: Count = 0 From the Library of Wow! eBook 756 Chapter 19: Synchronization and More Multithreading Patterns Note that calls to Monitor.Enter() and Monitor.Exit() are associated with each other by sharing the same object reference passed as... Monitor.Enter()) to produce again In this example, only one thread can enter the ready queue at a time, ensuring that there is no “consumption” without “production” and vice versa From the Library of Wow! eBook Synchronization 757 Using the lock Keyword Because of the frequent need for synchronization using Monitor in multithreaded code, and the fact that the try/finally block could easily be forgotten,... { lock (_Sync) { _Count++; } } task.Wait(); Console.WriteLine("Count = {0}", _Count); } static void Decrement() { for (int i = 0; i < _Total; i++) { lock (_Sync) { _Count ; } From the Library of Wow! eBook 758 Chapter 19: Synchronization and More Multithreading Patterns } } } OUTPUT 19.3: Count = 0 By locking the section of code accessing _Count (using either lock or Monitor), you make the Main() and . thread.Join(); OUTPUT 18. 9: From the Library of Wow! eBook ptg Chapter 18: Multithreading 7 40 Like the output of Listing 18. 9, which used TPL, Listing 18. 18 s code. Encrypt(item)).ToList(); } public static void Main() { List<string> data = Utility.GetData( 100 000 0).ToList(); Console.WriteLine("Push ENTER to exit."); Task task = Task.Factory.StartNew((). application domain’s UnhandledException event. Listing 18. 20 demonstrates that process, and Output 18. 10 shows the results. Listing 18. 20: Registering for Unhandled Exceptions using System; using

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

w