Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 95 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
95
Dung lượng
1,91 MB
Nội dung
CHAPTER 15 ■ PARALLEL PROGRAMMING 735 requested became available—this example waits for all of the tasks to complete before obtaining the results. using System; using System.Threading; using System.Threading.Tasks; namespace Recipe15_03 { class Recipe15_03 { static void Main(string[] args) { Console.WriteLine("Press enter to start"); Console.ReadLine(); // Create the tasks. Task<int> task1 = Task<int>.Factory.StartNew(() => writeDays()); Task<int> task2 = Task<int>.Factory.StartNew(() => writeMonths()); Task<int> task3 = Task<int>.Factory.StartNew(() => writeCities()); // Wait for all of the tasks to complete. Task.WaitAll(task1, task2, task3); // Get the results and write them out. Console.WriteLine("{0} days were written", task1.Result); Console.WriteLine("{0} months were written", task2.Result); Console.WriteLine("{0} cities were written", task3.Result); // Wait to continue. Console.WriteLine("\nMain method complete. Press Enter"); Console.ReadLine(); } static int writeDays() { string[] daysArray = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; foreach (string day in daysArray) { Console.WriteLine("Day of the Week: {0}", day); Thread.Sleep(500); } return daysArray.Length; } CHAPTER 15 ■ PARALLEL PROGRAMMING 736 static int writeMonths() { string[] monthsArray = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; foreach (string month in monthsArray) { Console.WriteLine("Month: {0}", month); Thread.Sleep(500); } return monthsArray.Length; } static int writeCities() { string[] citiesArray = { "London", "New York", "Paris", "Tokyo", "Sydney" }; foreach (string city in citiesArray) { Console.WriteLine("City: {0}", city); Thread.Sleep(500); } return citiesArray.Length; } } } 15-4. Parallel Process a Collection Problem You need to parallel process each element in a collection. Solution Use the System.Threading.Parallel.ForEach method to create a new task to process each of the elements in a collection. Optionally, use System.Threading.ParallelOptions to limit the degree of parallelism that will be used. How It Works The static Parallel.ForEach method accepts a collection, a function delegate, and an optional instance of ParallelOptions as arguments. A new task is created to process each element in the collection using the function referenced by the delegate. The number of concurrent tasks is controlled by the ParallelOptions.MaxDegreeOfParallelism property—a value of -1 means that the degree of parallelism CHAPTER 15 ■ PARALLEL PROGRAMMING 737 will be determined by the runtime, whereas a value of 1 or more limits the number of tasks that will run at the same time (a value of 0 will throw an exception). The Code The following example creates tasks to process each element of a simple array using the printNumbers method. We have called Thread.Sleep in this method to slow down the processing so that the example is clearer. We use the MaxDegreeOfParallelism property of ParallelOptions to ensure that at most two tasks are performed simultaneously—when running the example, notice that the output from the first two tasks is intermingled and then followed by the output from the third task. using System; using System.Threading; using System.Threading.Tasks; namespace Recipe15_04 { class Recipe15_04 { static void Main(string[] args) { Console.WriteLine("Press enter to start"); Console.ReadLine(); // Define the data we want to process. int[] numbersArray = { 100, 200, 300 }; // Configure the options. ParallelOptions options = new ParallelOptions(); options.MaxDegreeOfParallelism = 2; // Process each data element in parallel. Parallel.ForEach(numbersArray, options, baseNumber => printNumbers(baseNumber)); Console.WriteLine("Tasks Completed. Press Enter"); Console.ReadLine(); } static void printNumbers(int baseNumber) { for (int i = baseNumber, j = baseNumber + 10; i < j; i++) { Console.WriteLine("Number: {0}", i); Thread.Sleep(100); } } } } CHAPTER 15 ■ PARALLEL PROGRAMMING 738 15-5. Chain Tasks Together Problem You need to perform several tasks in sequence. Solution Create an instance of Task for the initial activity using the class constructors (as shown in the previous recipes in this chapter), and then call the ContinueWith method to create a Task instance representing the next activity in the sequence. When you have created all of the Task instances you require, call the Start method on the first in the sequence. How It Works The Task.ContinueWith and Task.ContinueWith<> methods create a new task that will continue upon completion of the Task instance on which they are invoked. The previous task (known as the antecedent) is provided as an input parameter to the lambda expression in the ContinueWith method—this can be used to check the states or get the result of the previous task, as shown in the following example. The Code The example for this recipe chains three tasks together. The first task adds some integer values. The second obtains the result from the first and prints it out, and the third task simply writes a message without reference to the previous tasks at all. using System; using System.Threading; using System.Threading.Tasks; namespace Recipe15_05 { class Recipe15_05 { static void Main(string[] args) { Console.WriteLine("Press enter to start"); Console.ReadLine(); // Create the set of tasks. Task<int> firstTask = new Task<int>(() => sumAndPrintNumbers(100)); Task secondTask = firstTask.ContinueWith(parent => printTotal(parent)); Task thirdTask = secondTask.ContinueWith(parent => printMessage()); CHAPTER 15 ■ PARALLEL PROGRAMMING 739 // Start the first task. firstTask.Start(); // Read a line to keep the process alive. Console.WriteLine("Press enter to finish"); Console.ReadLine(); } static int sumAndPrintNumbers(int baseNumber) { Console.WriteLine("sum&print called for {0}", baseNumber); int total = 0; for (int i = baseNumber, j = baseNumber + 10; i < j; i++) { Console.WriteLine("Number: {0}", i); total += i; } return total; } static void printTotal(Task<int> parentTask) { Console.WriteLine("Total is {0}", parentTask.Result); } static void printMessage() { Console.WriteLine("Message from third task"); } } } 15-6. Write a Cooperative Algorithm Problem You need to write a parallel algorithm with multiple phases, each of which must be completed before the next can begin. Solution Create an instance of the System.Threading.Barrier class and call the SignalAndWait method from your Task code at the end of each algorithm phase. CHAPTER 15 ■ PARALLEL PROGRAMMING 740 How It Works The Barrier class allows you to wait for a set of tasks to complete one part of an algorithm before moving onto the next. This is useful when the overall results from the one phase are required by all tasks in order to complete a subsequent phase. When creating an instance of Barrier, you specify an integer as a constructor argument. In your Task code, you call the SignalAndWait method when you have reached the end of a phase—your Task will block until the specified number of Tasks is waiting, at which point the Barrier allows all of the waiting tasks to continue into the next phase. It is up to you to determine what constitutes each phase of your algorithm and to specify how many Tasks must reach the barrier before the next phase can begin. You can also specify an action to be performed when each phase is completed (i.e., after the required number of tasks have called the SignalAndWait method, but before the tasks are allowed to continue to the next phase—the example for this recipe demonstrates how to do this with a lambda function. ■ Note It is important to ensure that you set the Barrier instance to expect the correct number of tasks at each stage of your algorithm. If you tell the Barrier to expect too few tasks, one phase may not have completed before the next begins. If you tell the Barrier to expect too many tasks, a phase will never start, even though all of your tasks have completed the earlier phase. You can change the number of tasks a Barrier will wait for by using the AddParticipant, AddParticipants, RemoveParticipant, and RemoveParticipants methods. The Code The following example shows a simple two-phase cooperative algorithm, performed by three tasks. When all of the tasks reach the barrier at the end of each phase, the notifyPhaseEnd method is called. using System; using System.Threading; using System.Threading.Tasks; namespace Recipe15_06 { class Recipe15_06 { static void Main(string[] args) { // Create the barrier. Barrier myBarrier = new Barrier(3, (barrier) => notifyPhaseEnd(barrier)); Task task1 = Task.Factory.StartNew( () => cooperatingAlgorithm(1, myBarrier)); Task task2 = Task.Factory.StartNew( () => cooperatingAlgorithm(2, myBarrier)); CHAPTER 15 ■ PARALLEL PROGRAMMING 741 Task task3 = Task.Factory.StartNew( () => cooperatingAlgorithm(3, myBarrier)); // Wait for all of the tasks to complete. Task.WaitAll(task1, task2, task3); // Wait to continue. Console.WriteLine("\nMain method complete. Press Enter"); Console.ReadLine(); } static void cooperatingAlgorithm(int taskid, Barrier barrier) { Console.WriteLine("Running algorithm for task {0}", taskid); // Perform phase one and wait at the barrier. performPhase1(taskid); barrier.SignalAndWait(); // Perform phase two and wait at the barrier. performPhase2(taskid); barrier.SignalAndWait(); } static void performPhase1(int taskid) { Console.WriteLine("Phase one performed for task {0}", taskid); } static void performPhase2(int taskid) { Console.WriteLine("Phase two performed for task {0}", taskid); } static void notifyPhaseEnd(Barrier barrier) { Console.WriteLine("Phase has concluded"); } } } 15-7. Handle Exceptions in Tasks Problem You need to catch and process exceptions thrown by a Task. CHAPTER 15 ■ PARALLEL PROGRAMMING 742 Solution Call the Task.Wait or Task.WaitAll methods within a try catch block to catch the System.AggregateException exception. Call the Handle method of AggregateException with a function delegate—the delegate will receive each exception that has been thrown by the Tasks. Your function should return true if the exception can be handled, and false otherwise. How It Works Catching AggregateException as it is thrown from Task.Wait or Task.WaitAll allows you to be notified of exceptions that are unhandled by your Task. If an error has occurred, then you will catch a single instance of System.AggregateException representing all of the exceptions that have been thrown. You process each individual exception by calling the AggregateException.Handle method, which accepts a function delegate (usually specified using a lambda expression)—the delegate will be called once for each exception that has been thrown by your task or tasks. Bear in mind that several threads may have encountered the same problem, and that you are likely to have to process the same exception type more than once. If you can handle the exception, your function delegate should return true— returning false will cause your application to terminate. ■ Tip If you do not catch exceptions from Wait or WaitAll, then any exception thrown by a Task will be considered unhandled and terminate your application. The Code The following example demonstrates how use the AggregateException.Handle method to implement a custom exception handler function: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Recipe15_07 { class Recipe15_07 { static void Main(string[] args) { // Create two tasks, one with a null param. Task goodTask = Task.Factory.StartNew(() => performTask("good")); Task badTask = Task.Factory.StartNew(() => performTask("bad")); CHAPTER 15 ■ PARALLEL PROGRAMMING 743 try { Task.WaitAll(goodTask, badTask); } catch (AggregateException aggex) { aggex.Handle(ex => handleException(ex)); } // Wait to continue. Console.WriteLine("\nMain method complete. Press Enter"); Console.ReadLine(); } static bool handleException(Exception exception) { Console.WriteLine("Processed Exception"); Console.WriteLine(exception); // Return true to indicate we have handled the exception. return true; } static void performTask(string label) { if (label == "bad") { Console.WriteLine("About to throw exception."); throw new ArgumentOutOfRangeException("label"); } else { Console.WriteLine("performTask for label: {0}", label); } } } } 15-8. Cancel a Task Problem You need to cancel a Task while it is running. Solution Create an instance of System.Threading.CancellationTokenSource and call the Token property to obtain a System.Threading.CancellationToken. Pass a function delegate that calls the Cancel method of your Task CHAPTER 15 ■ PARALLEL PROGRAMMING 744 to the Register method of CancellationToken. Cancel your Task by calling the Cancel method of CancellationTokenSource. How It Works The System.Threading.CancellationTokenSource class provides a mechanism to cancel one or more tasks. CancellationTokenSource is a factory for System.Threading.CancellationToken. CancallationToken has the property IsCancellationRequested, which returns true when the Cancel method is called on the CancellationTokenSource that produced the token. You can also use the Register method to specify one or more functions to be called when the Cancel method is called. The sequence for handling cancellation is as follows: 1. Create an instance of CancellationTokenSource. 2. Create one or more Tasks to handle your work, passing CancellationToken as a constructor parameter. 3. For each Task you have created, obtain a CancellationToken by calling Token on the CancellationTokenSource created in step 1. 4. Check the IsCancellationRequested property of the token in your Task body— if the property returns true, then release any resources and throw an instance of OperationCanceledException. 5. When you are ready to cancel, call the Cancel method on the CancellationTokenSource from step 1. Note that you must throw an instance of OperationCanceledException to acknowledge the task cancellation request. The Code The following example creates a CancellationToken that is used to create an instance of Task. A method to be called when the CancellationTokenSource is canceled is registered with the Register method. When CancellationTokenSource.Cancel is called, the Task is stopped and a message is written to the console. using System; using System.Threading; using System.Threading.Tasks; namespace Recipe15_08 { class Recipe15_08 { static void Main(string[] args) { // Create the token source. CancellationTokenSource tokenSource = new CancellationTokenSource(); // create the cancellation token CancellationToken token = tokenSource.Token; [...]... XElement("name", "orange"), new XElement("name", "grape"), new XElement("name", "fig"), new XElement("name", "plum"), new XElement("name", "banana"), new XElement("name", "cherry") ); } 752 CHAPTER 16 ■ USING LINQ static DataTable createDataTable() { DataTable table = new DataTable(); table.Columns.Add("name", typeof(string)); string[] fruit = { "apple", "orange", "grape", "fig", "plum", "banana", "cherry"... firstDataSource join f in secondDataSource on e.CommonKey equals f.CommonKey LINQ will arrange the data so that your filter and select statements are called once per common key You can refer to the individual elements using the variable names you have defined—in the fragment, we have used e and f You can join as many data sources as you wish in a LINQ query, as long as they share a common key 762 CHAPTER... System.Threading; System.Threading.Tasks; System.Collections.Concurrent; namespace Recipe15_9 { class Recipe15_9 { static void Main(string[] args) { // Create a concurrent collection ConcurrentStack cStack = new ConcurrentStack(); // create tasks that will use the stack Task task1 = Task.Factory.StartNew( () => addNumbersToCollection(cStack)); Task task2 = Task.Factory.StartNew( () => addNumbersToCollection(cStack));... for another The Code The following example performs a basic LINQ query on a string array, a collection, an XML tree, and a DataTable, and prints out the results in each case: using using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Xml.Linq; System.Data; namespace Apress.VisualCSharpRecipes.Chapter16 { class Recipe16_01 { static void Main(string[] args)... This chapter shows you how to build on those simple examples to exploit the full flexibility of LINQ One of the best features of LINQ is that you can perform the same kinds of queries whatever the data source is Each of the recipes in this chapter uses an array or a collection as the data source, but the same techniques can be applied equally to XML or databases The recipes in this chapter are all self-contained... because arrays and all standard generic collections implement IEnumerable, as follows: IEnumerable myEnum = from e in myarray select e; IEnumerable myEnum = from e in mycollection select e; If you are using an XML tree, you can get an IEnumerable from the root XElement by calling the Elements method; and for a DataTable, you can get an IEnumerable by calling the AsEnumerable method, as... has changed—we are querying a data source that contains myType instances, but selecting a string property—therefore, the result is an IEnumerable IEnumerable can be used with a foreach loop to enumerate the results of a query, but because LINQ queries return instances of IEnumerable and LINQ data sources must implement IEnumerable, you can also use the result of one query as the data... and databases in the same way To select all of the items in a data source is a simple two-step process 1 Start a new LINQ query using the from keyword, providing an element variable name that you will use to refer to elements that LINQ finds (for example, from e in datasource) 2 Indicate what will be added to the result set from each matching element using the select keyword For the basic “select all”... type annotation when calling the method—this determines the type of IEnumeration that will be returned The Range extension method takes a start index and a count as method parameters and returns a subset of the elements in the data source Skip, Take, and Range all return IEnumeration, so the results from these methods can be used either to enumerate the results or as the data source for another... your select statement to create an anonymous type 760 CHAPTER 16 ■ USING LINQ How It Works If you want to create a LINQ result that contains the values from more than one member of a data element, you can use the new keyword after the select keyword to create an anonymous type An anonymous type doesn’t have a name (hence “anonymous”) and is made up of just the values that you specify You reference . your tasks have completed the earlier phase. You can change the number of tasks a Barrier will wait for by using the AddParticipant, AddParticipants, RemoveParticipant, and RemoveParticipants. System.Threading.Barrier class and call the SignalAndWait method from your Task code at the end of each algorithm phase. CHAPTER 15 ■ PARALLEL PROGRAMMING 740 How It Works The Barrier class allows. instance of CancellationTokenSource. 2. Create one or more Tasks to handle your work, passing CancellationToken as a constructor parameter. 3. For each Task you have created, obtain a CancellationToken