.NET 4.5 Parallel Extensions Cookbook


.NET 4.5 Parallel Extensions Cookbook 80 recipes to create scalable, task-based parallel programs using .NET 4.5 Bryan Freeman BIRMINGHAM - MUMBAI .NET 4.5 Parallel Extensions Cookbook Copyright © 2013 Packt Publishing All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews. Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing, and its dealers and distributors will be held liable for any damages caused or alleged to be caused directly or indirectly by this book. Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information. First published: July 2013 Production Reference: 1190713 Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK ISBN 978-1-84969-022-5 www.packtpub.com Cover Image by Karl Moore (karl@karlmoore.co.uk) Credits Author Bryan Freeman Reviewers Carlos Hulot Nauzad Kapadia Jason DE Oliveira Steven M. Swafford Ken Tucker Ariel Woscoboinik Acquisition Editors Aarthi Kumaraswamy Kunal Parikh Lead Technical Editor Arun Nadar Technical Editors Athira Laji Mrunmayee Patil Priya Singh Project Coordinator Apeksha Chitnis Proofreader Elinor Perry-Smith Indexer Rekha Nair Production Coordinator Manu Joseph Cover Work Manu Joseph About the Author Bryan Freeman is a developer, architect, and writer on Parallel and Distributed Technologies. With over 15 years of experience delivering solutions across industry sectors such as healthcare, finance, and transportation, he specializes in technologies such as Microsoft .NET, Windows Communication Foundation, Windows Workflow, and Windows Azure to help clients deliver business value and drive revenue while reducing operational costs. As an active speaker, writer, and blogger, Bryan is a passionate community advocate for those who develop solutions based on .NET technologies. He is also an active member of the Scottsdale Institute and the Veterans of Foreign Wars. I would like to thank my beautiful wife Laura, and my daughter Isabelle for being there for me while I spent my evenings writing. Laura and Isabelle have endured the demands of my career with unwavering support. About the Reviewers Carlos Hulot has been working in the IT area for more than 20 years in different capabilities, from Software Development, Project Management, to IT Marketing Product Development and Management. Carlos has worked for multinational companies such as Royal Philips Electronics, PriceWaterhouseCoopers, and Microsoft. Currently he is working as an independent IT Consultant. Carlos is a Computer Science lecturer at two Brazilian universities. He holds a Ph.D in Computer Science and Electronics from the University of Southampton, UK and a B.Sc. in Physics from University of São Paulo, Brazil. Nauzad Kapadia is an independent professional and founder of Quartz Systems, and provides training and consulting services for the entire Microsoft .NET and SQL Server stack. Nauzad has over 17 years of industry experience and has been a regular speaker at events like TechED, DevCon, DevDays, and user group events. He has been a Microsoft MVP (Most Valuable Professional) for 6 years on technologies ranging from C#, ASP.NET, to SQL Server. Whenever he is not working on his computer, he enjoys rock music, photography, and reading. Jason De Oliveira works as CTO for Cellenza (http://www.cellenza.com), an IT Consulting Company specialized in Microsoft Technologies and Agile Methodology in Paris (France). He is an experienced Manager and Senior Solutions Architect, with high-level skills in Software Architecture and Enterprise Architecture. Jason works for big companies and helps them to realize complex and challenging software projects. He frequently collaborates with Microsoft and you can find him quite often at the Microsoft Technology Center (MTC) in Paris. He loves sharing his knowledge and experience via his blog, by speaking at conferences, writing technical books, writing articles in the technical press, giving software courses as MCT, and by coaching co-workers in his company. In 2011, Microsoft gave him the Microsoft® Most Valuable Professional (MVP C#) Award for his numerous contributions to the Microsoft community. Microsoft seeks to recognize the best and brightest from technology communities around the world with the MVP Award. These exceptional and highly respected individuals come from more than 90 countries, serving their local online and offline communities, and having an impact worldwide. Jason is very proud to be one of them. Please feel free to contact him via his blog if you need any technical assistance or want to exchange on technical subjects (http://www.jasondeoliveira.com). Jason has worked on the following books: ff .Net Framework 4.5 Expert Programming Cookbook (English) ff WCF 4.5 Multi-Layer Services Development with Entity Framework (English) ff Visual Studio 2012 - Développez pour le web (French) I would like to thank my lovely wife Orianne and my beautiful daughter Julia for supporting me in my work and for accepting long days and short nights during the week and sometimes even during the weekend. My life would not be the same without them! Steven M. Swafford is a highly motivated Information Technology Professional with 18 years of experience who is result-oriented, and is a strong team player, high-energy, hands-on professional, self-motivated, and detail-oriented. Able to meet challenging deadlines both individually and as part of or leader of a team. Quickly adapts at overcoming obstacles and implementing organizational change. Currently working as a Software Engineer, Steven develops and maintains web-based software solutions. As a skilled professional he is focused on the design and creation of software. Because communication skills are extremely important, Steven continues to expand his knowledge in order to communicate clearly with all facets of business. Recently Steven has been leading efforts to standardize software development tools and technology, plans and coordinates web accessibility as applied to Information Technology (IT) Solutions, and he is tackling application security in terms of best practice and implementation of the Security Development Lifecycle (SDL). Steven holds a Bachelor’s Degree in Computer Science and a Master’s Degree in Cyber security. Steven also holds both the CompTIA Network+ and Security+ certifications. As of January 2013, Steven was promoted to the position of IT Principal Accessibility Manager. This job requires Steven to draw upon his experience to ensure Information, Communications, and Technology (ICT) is accessible under Section 508 and the Web Content Accessibility Guidelines (WCAG). Ken Tucker has been given the Microsoft Most Valuable Professional (MVP) from 2003 till present and is working at SeaWorld Parks & Entertainment. I would like to thank my wife Alice-Marie for her support. Ariel Woscoboinik graduated as a Bachelor of Information Technology from the University of Buenos Aires and as an IT Technician from ORT schools. Since his childhood, he has been programing and getting more and more involved in the world of technology. Later on, he became interested in organizations and their business models and succeeded in converging both interests into his career: looking for the best solutions to involve people, processes, and technology. Currently, he works as a Software Development Manager for Telefe, the leading TV channel in Argentina. Ariel has been working with Microsoft Technologies since high school. During his career, he has worked for highly prestigious companies from Myriad industries: Microsoft, MAE, Intermex LLC, Pfizer, Monsanto, Banco Santander, IHSA, Disco S.A., Grupo Ecosistemas, Perception Group, Conuar. Among his passions are drama, (as he is an amateur actor), film-watching, soccer, and travel around the world. www.PacktPub.com Support files, eBooks, discount offers and more You might want to visit www.PacktPub.com for support files and downloads related to your book. Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.PacktPub.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at service@packtpub.com for more details. At www.PacktPub.com, you can also read a collection of free technical articles, sign up for a range of free newsletters and receive exclusive discounts and offers on Packt books and eBooks. TM http://PacktLib.PacktPub.com Do you need instant solutions to your IT questions? PacktLib is Packt’s online digital book library. Here, you can access, read and search across Packt’s entire library of books. Why Subscribe? ff Fully searchable across every book published by Packt ff Copy and paste, print and bookmark content ff On demand and accessible via web browser Free Access for Packt account holders If you have an account with Packt at www.PacktPub.com, you can use this to access PacktLib today and view nine entirely free books. Simply use your login credentials for immediate access. Table of Contents Preface 1 Chapter 1: Getting Started with Task Parallel Library 7 Introduction 7 Creating a task 9 Waiting for tasks to finish 13 Returning results from a task 18 Passing data to a task 21 Creating a child task 24 Lazy task execution 27 Handling task exceptions using try/catch block 29 Handling task exceptions with AggregateException.Handle 33 Cancelling a task 35 Cancelling one of many tasks 39 Chapter 2: Implementing Continuations 45 Introduction 45 Continuing a task 46 Passing task results to a continuation 49 Continue "WhenAny" and "WhenAll" 53 Specifying when a continuation will run 58 Using a continuation for exception handling 62 Cancelling a continuation 65 Using a continuation to chain multiple tasks 70 Using a continuation to update a UI 73 Chapter 3: Learning Concurrency with Parallel Loops 79 Introduction 79 Creating a basic parallel for loop 80 Creating a basic parallel foreach loop 83 Breaking a parallel loop 86 ii Table of Contents Stopping a parallel loop 89 Cancelling a parallel loop 92 Handling exceptions in a parallel loop 95 Controlling the degree of parallelism in a loop 98 Partitioning data in a parallel loop 102 Using Thread Local Storage 106 Chapter 4: Parallel LINQ 111 Introduction 111 Creating a basic parallel query 112 Preserving order in parallel LINQ 115 Forcing parallel execution 117 Limiting parallelism in a query 120 Processing query results 124 Specifying merge options 127 Range projection with parallel LINQ 130 Handling exceptions in parallel LINQ 131 Cancelling a parallel LINQ query 136 Performing reduction operations 139 Creating a custom partitioner 142 Chapter 5: Concurrent Collections 147 Introduction 147 Adding and removing items to BlockingCollection 148 Iterating a BlockingCollection with GetConsumingEnumerable 151 Performing LIFO operations with ConcurrentStack 154 Thread safe data lookups with ConcurrentDictionary 157 Cancelling an operation in a concurrent collection 160 Working with multiple producers and consumers 164 Creating object pool with ConcurrentStack 167 Adding blocking and bounding with IProducerConsumerCollection 171 Using multiple concurrent collections to create a pipeline 180 Chapter 6: Synchronization Primitives 183 Introduction 183 Using monitor 184 Using a mutual exclusion lock 188 Using SpinLock for synchronization 192 Interlocked operations 195 Synchronizing multiple tasks with a Barrier 197 Using ReaderWriterLockSlim 200 iii Table of Contents Using WaitHandles with Mutex 204 Waiting for multiple threads with CountdownEvent 207 Using ManualResetEventSlim to spin and wait 211 Using SemaphoreSlim to limit access 214 Chapter 7: Profiling and Debugging 217 Introduction 217 Using the Threads and Call Stack windows 218 Using the Parallel Stacks window 222 Watching values in a thread with Parallel Watch window 225 Detecting deadlocks with the Parallel Tasks window 226 Measure CPU utilization with Concurrency Visualizer 230 Using Concurrency Visualizer Threads view 234 Using Concurrency Visualizer Cores view 238 Chapter 8: Async 241 Introduction 241 Creating an async method 242 Handling Exceptions in asynchronous code 247 Cancelling an asynchronous operation 251 Cancelling async operation after timeout period 256 Processing multiple async tasks as they complete 261 Improving performance of async solution with Task.WhenAll 265 Using async for file access 269 Checking the progress of an asynchronous task 275 Chapter 9: Dataflow Library 281 Introduction 281 Reading from and writing to a dataflow block synchronously 283 Reading from and writing to a dataflow block asynchronously 286 Implementing a producer-consumer dataflow pattern 289 Creating a dataflow pipeline 293 Cancelling a dataflow block 297 Specifying the degree of parallelism 302 Unlink dataflow blocks 305 Using JoinBlock to read from multiple data sources 309 Index 315 Preface The multicore processor boom is fully underway. It is becoming difficult to find a server, desktop, or laptop that doesn't have at least two processor cores. There are even several models of dual core mobile phones on the market. However, to fully realize the power of modern multicore processors, developers need to be able to effectively recognize opportunities for parallelization and write parallel code. Largely due to the complexities of creating and managing the lifecycle of classic threads, parallel coding is not routinely practiced by the large majority of developers. To help ease the complexities of writing parallel code, Microsoft has introduced the Parallel Extensions in .NET framework. The Parallel Extensions or Task Parallel Library helps us to write efficient, scalable, parallel code in a natural manner without having to deal directly with the classic threading model or the thread pool. Using these extensions, we can conveniently write parallel versions of existing sequential code that can automatically utilize all available processors, thus providing significant performance gains. This book provides coverage of the major classes of the .Net 4.5 Parallel extensions from front to back, and will provide a developer with no multithreaded development experience ability to write highly scalable asynchronous applications that take advantage of today's hardware. What this book covers Chapter 1, Getting Started with Task Parallel Library, looks at the various ways to create and start a task, cancel a task, handle exceptions in a task, and control the behavior of a task. At the core of the .NET Parallel Extensions is the System.Threading.Task class. Chapter 2, Implementing Continuations, helps us learn how to create task continuations and child tasks. We will also learn how to control the condition under which continuations run, how to handle exceptions using continuations, and how to chain tasks and continuations together to form a pipeline pattern. In asynchronous programming, it is very common for one asynchronous operation to invoke a second operation and pass data to it. Preface 2 Chapter 3, Learning Concurrency with Parallel Loops, helps us learn how to use parallel loops to process large collections of data in parallel. We will also learn how to control the degree of parallelism in loops, break out of loops, and partition source data for loop processing. Chapter 4, Parallel LINQ, helps us learn how to create a parallel LINQ query, control query options such as results merging, and the degree of parallelism for the query. Parallel LINQ is a parallel implementation of LINQ to Objects. Chapter 5, Concurrent Collections, helps us learn how to use concurrent collections as a data buffer in a producer-consumer pattern, how to add blocking and bounding to a custom collection, and how to use multiple concurrent collections for forming a pipeline. Concurrent collections in .NET Framework 4.5 allows developers to create type-safe as well as thread-safe collections. Chapter 6, Synchronization Primitives, helps us look at the synchronization primitives available in the .NET Framework, and how to use the new lightweight synchronization primitives. Parallel applications usually need to manage access to some kind of shared data. Chapter 7, Profiling and Debugging, helps us examine the parallel debugging and profiling tools build into Visual Studio 2012, and how to use them to debug and optimize our parallel applications. Chapter 8, Async, helps us learn about the Async feature of the .NET Framework, and using asynchrony to maintain a responsive UI. We've all seen client applications that don't respond to mouse events or update the display for noticeable periods of time due to synchronous code holding on to the single UI thread for too long. Chapter 9, Dataflow Library, helps us examine the various types of dataflow blocks provided by Microsoft, and how to use them to form a data processing pipeline. The Task Parallel Library's dataflow library is a new set of classes that are designed to increase the robustness of highly concurrent applications by using asynchronous message passing and pipelining to obtain more control and better performance than manual threading. What you need for this book This book assumes you have a solid foundation in general C# development. You should have at least a working knowledge of lambda expressions and delegates. You will need a Windows 7, Windows 8, or Windows Server 2008 R2 PC. A multicore or multiprocessor machine is not required, but is preferable. In addition, you will need Visual Studio 2012 and the .NET Framework 4.5. Preface 3 Who this book is for This book is intended to help experienced C# developers write applications that leverage the power of modern multicore processors. It provides the necessary knowledge for an experienced C# developer to work with .NET Parallel Extensions. Previous experience of writing multithreaded applications is not necessary. Conventions In this book, you will find a number of styles of text that distinguish between different kinds of information. Here are some examples of these styles, and an explanation of their meaning. Code words in text are shown as follows: "In this recipe, we will take a look at the basics of adding items to, and removing items from BlockingCollection." A block of code is set as follows: char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' }; const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold: [var result = Enumerable.Range(0, 10000).AsParallel() .WithExecutionMode(ParallelExecutionMode.ForceParallelism) .WithDegreeOfParallelism(2) … .Select((x, i) => i) .ToArray(); New terms and important words are shown in bold. Words that you see on the screen, in menus or dialog boxes for example, appear in the text like this: "clicking the Next button moves you to the next screen". Warnings or important notes appear in a box like this. Tips and tricks appear like this. Preface 4 Reader feedback Feedback from our readers is always welcome. Let us know what you think about this book—what you liked or may have disliked. Reader feedback is important for us to develop titles that you really get the most out of. To send us general feedback, simply send an e-mail to feedback@packtpub.com, and mention the book title via the subject of your message. If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, see our author guide on www.packtpub.com/authors. Customer support Now that you are the proud owner of a Packt book, we have a number of things to help you to get the most from your purchase. Downloading the example code You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you. Errata Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you find a mistake in one of our books—maybe a mistake in the text or the code—we would be grateful if you would report this to us. By doing so, you can save other readers from frustration and help us improve subsequent versions of this book. If you find any errata, please report them by visiting http://www.packtpub.com/submit-errata, selecting your book, clicking on the errata submission form link, and entering the details of your errata. Once your errata are verified, your submission will be accepted and the errata will be uploaded on our website, or added to any list of existing errata, under the Errata section of that title. Any existing errata can be viewed by selecting your title from http://www.packtpub.com/support. Piracy Piracy of copyright material on the Internet is an ongoing problem across all media. At Packt, we take the protection of our copyright and licenses very seriously. If you come across any illegal copies of our works, in any form, on the Internet, please provide us with the location address or website name immediately so that we can pursue a remedy. Preface 5 Please contact us at copyright@packtpub.com with a link to the suspected pirated material. We appreciate your help in protecting our authors, and our ability to bring you valuable content. Questions You can contact us at questions@packtpub.com if you are having a problem with any aspect of the book, and we will do our best to address it. 1 Getting Started with Task Parallel Library In this chapter, we will cover the following recipes: ff Creating a task ff Waiting for tasks to finish ff Returning results from a task ff Passing data to a task ff Creating a child task ff Lazy task execution ff Handling task exceptions using try/catch block ff Handling task exceptions with AggregateException.Handle ff Cancelling a task ff Cancelling one of many tasks Introduction At the beginning of the personal computer era, there was no concept of multiple threads offered by an operating system. Typically, operating system code and application code ran on a single thread of execution. The problem with this was that if a single application misbehaved, or simply took a long time to execute, the whole machine would stall, and often had to be rebooted. Getting Started with Task Parallel Library 8 As the development of the Windows operating systems progressed, Microsoft realized that they needed to improve this situation. In the Windows NT kernel, each application runs in its own process. A process is a collection of resources in which a virtual address space is allocated for each application. The advent of these processes ensured that code and data being used by one application could not be accessed and corrupted by another application, thus improving the reliability of the system. Each process in Windows was also given its own thread. A thread is an operating system construct that functions like a virtual CPU. At any given moment, one of these threads is allowed to run on the physical CPU for a slice of time. When the time for a thread to run expires, it is swapped off of the CPU for another thread. Therefore, if a single thread enters an infinite loop, it can't monopolize all of the CPU time on the system. At the end of its time slice, it will be switched out for another thread. Over the years, computers with multiple processors began to appear. These multiple processor machines were able to execute multiple threads at once. It became possible for an application to spawn new threads to run a compute-bound process asynchronously, thus gaining a performance improvement. Over the past few years, the trend in processor development has shifted from making processors faster and faster, to making processors with multiple CPU cores on a single physical processor chip. Individuals who purchase these new machines expect their investment to pay off in terms of applications which are able to run efficiently across the available processor cores. Maximizing the utilization of the computing resources provided by the next generation of multi-core processors requires a change in the way the code is written. The .NET framework has supported writing multi-threaded applications from the beginning, but the complexity of doing so has remained just out of reach for many .NET developers. To fully take the advantage of multi-threading, you needed to know quite a bit about how Windows works under the hood. For starters, you had to create and manage your own threads, which can be a demanding task as the number of threads in an application grows, and can often be the source of hard-to-find bugs. Finally, help has arrived. Starting in .NET 4.0, Microsoft introduced the .NET Parallel Extensions, which gave us a new runtime, new class library types (the Task Parallel Library (TPL)), and new diagnostic tools to help with the inherent complexities of parallel programming. The TPL isn't just a collection of new types. It's a completely new way of thinking about parallel programming. No longer do we need to think in terms of threads. With the TPL, we can now think in terms of task. With this new task-based model, we just need to figure out the pieces of our application that can execute concurrently, and convert those pieces into tasks. The runtime will take care of managing and creating all of the underlying threads that actually do the work. The System.Threading.Task class in itself is just a wrapper for passing a delegate, which is a data structure that refers to a static method or to a class instance, and an instance method of that class. Chapter 1 9 A TPL task still uses the classic thread pool internally, but the heavy lifting of spinning up new threads to carry out the tasks and determining the optimum number of threads required to take full advantage of the hardware, is all done by the runtime. In this chapter, we will take a look at the basics of creating a parallel task. You will learn how to pass data into a Task using the Task state object, returning data from a Task, cancelling the Task, and handling exceptions within a Task. Creating a task Tasks are an abstraction in the .NET framework to represent asynchronous units of work. In some ways, a task resembles the creation of a classic .NET thread, but provides a higher level of abstraction, which makes your code easier to write and read. We will look at the three basic ways to create and run a new task. ff The Parallel.Invoke() method: This method provides an easy way to run any number of concurrent statements ff The Task.Start() method: This method starts a task and schedules it for execution with TaskScheduler ff The Task.Factory.StartNew() method: This method creates and starts a task using Task.Factory In this recipe, we will create a new task using each of these three methods. To give our tasks something to do, we will be using WebClient to read the text of three classic books. We will then split the words of each book into a string array, and display a count of the words in each book. How to do it… Ok, let's start building a Console application that demonstrates the various ways to create a parallel task. 1. Launch Visual Studio 2012. Getting Started with Task Parallel Library 10 2. Start a new project using the C# Console Application project template, and assign SimpleTasks as the Solution name as shown in the following screenshot: 3. Add the following using statements at the top of your Program class: using System; using System.Linq; using System.Net; using System.Threading.Tasks; Downloading the example code You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub. com/support and register to have the files e-mailed directly to you. Chapter 1 11 4. First, let's create a task using Parallel.Invoke. Add the following code to the Main method of the Program class: char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\ u000A' }; const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; Parallel.Invoke(() => { Console.WriteLine("Starting first task using Parallel. Invoke"); var client = new WebClient(); client.Headers.Add("user-agent", headerText); var words =client.DownloadString(@"http://www.gutenberg.org/ files/2009/2009.txt"); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); Console.WriteLine("Origin of Species word count: {0}", wordArray.Count()); client.Dispose(); } ); 5. Next, let's start task using the Start method of the Task object. Add the following code to the Main method of the Program class just below the code for the previous step: var secondTask = new Task(() => { Console.WriteLine("Starting second task using Task.Start"); var client = new WebClient(); client.Headers.Add("user-agent", headerText); var words = client.DownloadString(@"http://www.gutenberg.org/ files/16328/16328-8.txt"); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); Console.WriteLine("Beowulf word count: {0}", wordArray. Count()); client.Dispose(); } ); secondTask.Start(); Getting Started with Task Parallel Library 12 6. Finally, let's create task using Task.Factory.StartNew. Add the following code to the Main method of the Program class: Task.Factory.StartNew(() => { Console.WriteLine("Starting third task using Task.Factory. StartNew"); var client = new WebClient(); client.Headers.Add("user-agent", headerText); var words = client.DownloadString(@"http://www.gutenberg.org/ files/4300/4300.txt"); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); Console.WriteLine("Ulysses word count: {0}", wordArray. Count()); client.Dispose(); } ); //wait for Enter key to exit Console.ReadLine(); 7. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following screenshot. Note that the exact order of the text you see may vary as tasks run asynchronously: Chapter 1 13 How it works… The Parallel.Invoke method can implicitly create and run any number of statements concurrently by passing an action delegate for each delegate of work to be done. Parallel.Invoke(( )=>DoSomething( ), ( )=>DoSomethingElse( )); It is worth noting however, that the number of tasks actually created by Parallel.Invoke may or may not be equal to the number of delegates passed in, especially if there are a large number of delegates. Using Task.Start() or Task.Factory.StartNew() creates new tasks explicitly. The new tasks will be allocated threads by the ThreadPool class, which handles the actual creation of the threads the tasks use for carrying out their work. As developers, we are shielded from all of this thread creation work, because it is done for us by the Task object. When you create a task, you are really just creating a wrapper around a delegate of work to be performed. The delegate can be a named delegate and anonymous method, or a lambda expression. So, which of these methods of creating task is the best? Task.Factory.StartNew is usually the preferred method, because it is more efficient in terms of the synchronization costs. Some amount of synchronization cost is incurred when using Thread.Start, because it is necessary to ensure that another thread is not simultaneously calling start on the same Task object. When using Task.Factory.StartNew, we know that the task has already been scheduled by the time task reference is handed back to our code. Note also that you can't call Start() on a task that has already run and completed. If you need the tasks to do the work again, you need to create new task with the same delegate of work. For the remainder of this book, we will primarily be using Task.Factory.StartNew. Waiting for tasks to finish When developing a parallel application, you will often have situations where a task must be completed before the main thread can continue processing. The Task Parallel Library includes several methods that allow you to wait for one or more parallel tasks to complete. This recipe will cover two such methods: Task.Wait() and Task.WaitAll(). Getting Started with Task Parallel Library 14 In this recipe we will be creating three tasks, all of which read in the text classic books and produce a word count. After we create the first task, we will wait for it to complete using Task.Wait(), before starting the second and third task. We will then wait for both the second and third tasks to complete using Task.WaitAll() before writing a message to the console. How to do it… Let's create a Console application that demonstrates how to wait for task completion. 1. Launch Visual Studio 2012. 2. Start a new project using the C# Console Application project template, and assign WordCount as the Solution name. 3. Add the following using statements at the top of your Program class: using System; using System.Linq; using System.Net; using System.Threading.Tasks; Chapter 1 15 4. In the Main method of the Program class, add a character array containing the basic punctuation marks. We will use this array in string.Split() to eliminate punctuation marks. Also add a string constant for the user-agent header of the WebClient. char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\ u000A' }; const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; 5. OK, now let's create our first task. This task will use WebClient to read the Origin of Species by Darwin, and get its word count. Enter the following code in the Main method of the Program class just below the previous statement: var task1 = Task.Factory.StartNew(() => { Console.WriteLine("Starting first task."); var client = new WebClient(); client.Headers.Add("user-agent", headerText); var words = client.DownloadString(@"http://www.gutenberg.org/ files/2009/2009.txt"); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); Console.WriteLine("Origin of Species word count: {0}", wordArray.Count()); } ); 6. Now, just below the previous task, write the following statements to wait on the task, and write a message to the Console application: task1.Wait(); Console.WriteLine("Task 1 complete. Creating Task 2 and Task 3."); 7. Below the previous statement, enter the code to create the second and third tasks. These tasks are very similar to the first task. var task2 = Task.Factory.StartNew(() => { Console.WriteLine("Starting second task."); var client = new WebClient(); client.Headers.Add("user-agent", headerText); var words = client.DownloadString(@"http://www.gutenberg.org/ files/16328/16328-8.txt"); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); Getting Started with Task Parallel Library 16 Console.WriteLine("Beowulf word count: {0}", wordArray. Count()); }); var task3 = Task.Factory.StartNew(() => { Console.WriteLine("Starting third task."); var client = new WebClient(); client.Headers.Add("user-agent", headerText); var words = client.DownloadString(@"http://www.gutenberg.org/ files/4300/4300.txt"); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); Console.WriteLine("Ulysses word count: {0}", wordArray. Count()); }); 8. Finally, let's use Task.WaitAll() to wait for the second and third task to complete, then prompt the user to exit the program. Task.WaitAll() takes an array of task as its parameter, and can be used to wait for any number of tasks to complete. Task.WaitAll(task2,task3); Console.WriteLine("All tasks complete."); Console.WriteLine("Press to exit."); Console.ReadLine(); 9. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following screenshot. Note that the exact order of the last few lines of text may still vary depending on the execution order of the second and third tasks. Chapter 1 17 How it works… Although Task.Wait() and Task.WaitAll() are fairly self-explanatory, both have several overloads that offer different functionalities. Task.Wait() can take either an Int32 or TimeSpan parameter to specify a specific period of time to wait. It can also accept a CancellationToken token parameter for cancellation, which will be covered later in the chapter. Task.WaitAll() always takes an array of Task as its first parameter, and has a second parameter which can be an Int32 or TimeSpan as in Task.Wait. Another useful method not shown in the recipe is Task.WaitAny(). WaitAny is very similar to WaitAll, except that it waits for only one Task in the array of Task to complete. The first Task of Task array to finish, completes the wait condition, and execution of the main thread is allowed to move forward. It is important to note that when you call one of the Wait methods, the runtime will check to see if the task you are waiting on has started executing. If task has started executing, then the thread that called Wait will block until task has finished executing. However, if task has not started running, then the runtime may execute the task using the thread that calls Wait. The various overloads and behaviors of Task.Wait, Task.WaitAll, and Task.WaitAny are shown in the following table: Wait() Waits for the task to complete execution. Wait(CancellationToken) Waits for the task to complete execution or CancellationToken to be set. Wait(Int32) Waits for task to complete or number of milliseconds to pass. A value of -1 waits indefinitely. Wait(TimeSpan) Waits for the task to complete execution or specified timespan to pass. Wait(Int32, CancellationToken) Waits for task to complete, number of milliseconds to pass, or CancellationToken to be set. WaitAll(Task[]) Waits for all of the tasks in array to complete execution. WaitAll(Task[], Int32) Waits for all of the tasks in the array to complete execution or number of milliseconds to pass. A value of -1 waits indefinitely. Getting Started with Task Parallel Library 18 WaitAll(Task[], CancellationToken) Waits for all of the tasks in array to complete execution or for a CancellationToken to be set. WaitAll(Task[], TimeSpan) Waits for all of the tasks in array to complete execution or specified timespan to pass. WaitAll(Task[], Int32, CancellationToken) Waits for all of the tasks in array to complete execution, number of milliseconds to pass, or CancellationToken to be set. WaitAny(Task[]) Waits for any of the tasks in the array to complete execution. WaitAny(Task[], Int32) Waits for any of the tasks in array to complete execution or number of milliseconds to pass. A value of -1 waits indefinitely. WaitAny(Task[], CancellationToken) Waits for any of the tasks in array to complete execution or for a CancellationToken to be set. WaitAny(Task[], TimeSpan) Waits for any of the tasks in array to complete execution or specified timespan to pass. WaitAny(Task[], Int32, CancellationToken) Waits for any of the tasks in array to complete execution, number of milliseconds to pass, or CancellationToken to be set. Returning results from a task So far, our tasks have not returned any values. However, it is often necessary to return a result from a task so it can be used in another part of our application. This functionality is provided by the Result property of Task. In this recipe, we will be creating a solution similar with tasks similar to the previous solution, but each of our three tasks return a result which can then be used to display the word count to the user. How to do it… Let's go to Visual Studio and see how we can return result values from our tasks. 1. Start a new project using the C# Console Application project template, and assign WordCount2 as the Solution name. 2. Add the following using statements are at the top of your Program class: using System; using System.Linq; Chapter 1 19 using System.Net; using System.Threading.Tasks; 3. In the Main method of the Program class, add a character array containing the basic punctuation marks. We will use this array in string.Split() to eliminate punctuation marks. Also add a string constant for the WebClient user-agent header. char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\ u000A' }; const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; 4. Start by creating three tasks of type Task named task1, task2, and task3. Your tasks should look as shown in the following code snippet: Task task1 = Task.Factory.StartNew(() => { Console.WriteLine("Starting first task."); var client = new WebClient(); client.Headers.Add("user-agent", headerText); var words = client.DownloadString(@"http://www.gutenberg.org/ files/2009/2009.txt"); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); return wordArray.Count(); }); Task task2 = Task.Factory.StartNew(() => { Console.WriteLine("Starting second task."); var client = new WebClient(); client.Headers.Add("user-agent", headerText); var words = client.DownloadString(@"http://www.gutenberg.org/ files/16328/16328-8.txt"); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); return wordArray.Count(); }); Task task3 = Task.Factory.StartNew(() { Console.WriteLine("Starting third task."); var client = new WebClient(); client.Headers.Add("user-agent", headerText); Getting Started with Task Parallel Library 20 var words = client.DownloadString(@"http://www.gutenberg.org/ files/4300/4300.txt"); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); return wordArray.Count(); }); 5. Immediately below your tasks, add Console.Writeline() statements that use Task.Result to display the results to the user. The remainder of the Main method should now look as shown in the following code snippet: Console.WriteLine("task1 is complete. Origin of Species word count: {0}",task1.Result); Console.WriteLine("task2 is complete. Beowulf word count: {0}", task2.Result); Console.WriteLine("task3 is complete. Ulysses word count: {0}", task3.Result); Console.WriteLine("Press to exit."); Console.ReadLine(); 6. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following: How it works… Task subclasses the standard Task class and provides the additional feature of the ability to return a value. This is done by switching from providing an Action delegate to providing a Func delegate. Chapter 1 21 It is worth noting that calling the Task.Result accessor will ensure that the asynchronous operation is complete before returning, so this is another method of waiting for a task to complete. Once the result of Task is available, it will be stored and returned immediately on later calls to the Result accessor. Passing data to a task You can supply the data used by task by passing an instance of System.Action and an object representing the data to be used by the action. In this recipe, we will be revisiting our WordCount example, but this time we will be parameterizing the data the tasks will act upon. How to do it… The ability to pass data into a task allows us to create a single task that can operate on multiple pieces of input data. Let's create a Console application so we can see how this works: 1. Start a new project using the C# Console Application project template and assign WordCount3 as the Solution name. 2. Add the following using statements at the top of your Program class: using System; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Collections.Generic; 3. In the Main method of the Program class, add a character array containing the basic punctuation marks. We will use this array in string.Split() to eliminate punctuation marks. Also add a constant string for the WebClients user-agent task. char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\ u000A' }; const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; 4. For this recipe, let's create a new Dictionary instance that can hold our book titles and URLs. Immediately after the previous statement, add the following code to create and initialize the dictionary: var dictionary = new Dictionary { {"Origin of Species", "http://www.gutenberg.org/ files/2009/2009.txt"}, Getting Started with Task Parallel Library 22 {"Beowulf", "http://www.gutenberg.org/files/16328/16328-8.txt"}, {"Ulysses", "http://www.gutenberg.org/files/4300/4300.txt"} }; 5. This time we will be creating anonymous tasks in a loop. We still would like to wait for the tasks to complete before prompting the user to exit the program. We need a collection to hold our tasks, so we can pass them to Task.WaitAll() and wait for completion. Below the previous statement, create a List to hold our tasks. var tasks = new List(); 6. Next, we want to create a for loop to loop through KeyValuePairs in the dictionary. Let's put the for loop below the previous statement. foreach (var pair in dictionary) { } 7. Inside the body of your for loop, put the definition of task, and add it to your task list as follows. Note the KeyValuePair being passed into task is in the form of an object. In the delegate body, we cast this object back to a KeyValuePair. Other than that, task is pretty much the same. tasks.Add( Task.Factory.StartNew((stateObj) => { var taskData = (KeyValuePair)stateObj; var client = new WebClient(); client.Headers.Add("user-agent", headerText); var words = client.DownloadString(taskData.Value); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); Console.WriteLine("Word count for {0}: {1}", taskData.Key, wordArray.Count()); },pair)); 8. After the for loop, let's finish things up by waiting on the tasks to complete using Task.WaitAll() and prompting the user to exit. The last few lines should be as follows: Task.WaitAll(tasks.ToArray()); Console.WriteLine("Press to exit."); Console.ReadLine(); 9. In Visual Studio 2012, press F5 to run the project. You should see output as shown in the following screenshot: Chapter 1 23 How it works… By passing data to Task using the state feature, we now have a very powerful model for task creation, because we can create many tasks at once, each having the same code statements in the body and passing in the data that Task operates on. It also makes our code much more concise and readable. In our application we need to pass two items of data into the task: a book title and the URL of the book, so we created dictionary. var dictionary = new Dictionary { {"Origin of Species", "http://www.gutenberg.org/files/2009/2009. txt"}, {"Beowulf", "http://www.gutenberg.org/files/16328/16328-8.txt"}, {"Ulysses", "http://www.gutenberg.org/files/4300/4300.txt"} }; We would also want to wait on all of these tasks to complete before we prompt the user to exit, so we need to create a collection that can be converted to an array of tasks to hold our Task objects. In this case, we made a list of tasks. In the body of our look that creates the tasks, we will add the tasks to the list. var tasks = new List(); foreach (var pair in dictionary) Getting Started with Task Parallel Library 24 { tasks.Add( //TASK DECLARATION HERE )); } In our loop, we will pass in each of KeyValuePairs in dictionary as an object, using the Task(Action, Object) constructor. This syntax is just a bit odd because you actually refer to the state object twice. Task.Factory.StartNew((stateObj) => { // TASK Body },pair ));} The key takeaway here is that the only way to pass data to a Task constructor is using Action. To use the members of a specific type, you must convert or explicitly cast the data back to the desired type in the body of the Task. var taskData = (KeyValuePair)stateObj; Creating a child task Code that is running a task can create another task with the TaskCreationOptions. AttachedToParent set. In this case, the new task becomes a child of the original or parent task. In this recipe, we will be using a simplified version of the WordCount solution that uses a parent task to get the text of one book into a string array, and then spins up a child task to print the results. How to do it… Let's return to our WordCount solution, so we can see how to create a child task and attach it to a parent. 1. Start a new project using the C# Console Application project template, and assign WordCount4 as the Solution name. 2. Add the following using statements at the top of your Program class: using System; using System.Linq; using System.Net; using System.Threading.Tasks; Chapter 1 25 3. In the Main method of the Program class, add a character array containing the basic punctuation marks. We will use this array in string.Split() to eliminate punctuation marks. Also, add a constant string for the WebClient user-agent header. char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\ u000A' }; const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; 4. First, let's create the basic structure of our parent task. This is very similar to the other tasks we have created so far, and takes no parameters, and returns no values. Task parent = Task.Factory.StartNew(() => { Console.WriteLine("Parent task starting"); const string uri = "http://www.gutenberg.org/files/2009/2009. txt"; var client = new WebClient(); client.Headers.Add("user-agent", headerText); var book = client.DownloadString(uri); var wordArray = book.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); // Child Task will go here }); 5. Next, right after the comment in the parent task, let's create a child task to print the results and set the AttachedToParent option. Task.Factory.StartNew(()=> { Console.WriteLine("Child task starting"); Console.WriteLine("Word count for Origin of Species: {0}",wordArray.Count()); Console.WriteLine("Attached child task completed."); },TaskCreationOptions.AttachedToParent); 6. Finally, just below the close of the parent task, let's wait for the parent task to complete, and prompt the user to exit the application with the following code: parent.Wait(); Console.WriteLine("Parent task completed."); Console.WriteLine("Press to exit."); Console.ReadLine(); Getting Started with Task Parallel Library 26 7. That's pretty much it. In Visual Studio 2012, press F5 to run the project. You should see output as shown in the following screenshot: How it works… Using TaskCreationOptions.AttachedToParent expresses structured parallelism. The parent task will wait for the child task to finish, so at the end of our program, all we have to do is wait for the parent task. The nested child task, itself, is just an ordinary task created in the delegate of another task. A parent task may create any number of child tasks, limited only by system resources. You can also create a nested task without using TaskCreationOptions. AttachedToParent. The only real difference is that the nested tasks created without this option are essentially independent from the outer task. A task created with the TaskCreationOptions.AttachedToParent option set is very closely synchronized with the parent. The outer task could also use the DenyChildAttach option to prevent other tasks from attaching as child tasks. However, the same outer task could still create an independent nested task. Chapter 1 27 Lazy task execution Lazy initialization of an object means that object creation is deferred until the object is actually used by your program. If you have a parallel task that you want to execute only when the value returned from the task is actually needed, you can combine lazy task execution with the Task.Factory.StartNew method. In this recipe, we will return to our, by now familiar WordCount solution, to show you how to execute a parallel task and compute a word count for our book, only when we display the result to the console. How to do it… Let's create a console application that demonstrates how we can defer task creation until the result of the task is needed. 1. Start a new project using the C# Console Application project template, and assign WordCount5 as the Solution name. 2. Add the following using statements at the top of your Program class. using System; using System.Linq; using System.Net; using System.Threading.Tasks; 3. The first step is to declare System.Threading.Task for lazy initialization. In the Main method of your Program class, put a Lazy declaration as follows: var lazyCount = new Lazy>(()= { //Task declaration and body go here }); 4. Inside the Lazy initialization declaration, place the code to create to task. The entire statement should now look as the following code snippet: Task.Factory.StartNew(() => { Console.WriteLine("Executing the task."); char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' }; const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; const string uri = "http://www.gutenberg.org/files/2009/2009. txt"; var client = new WebClient(); client.Headers.Add("user-agent", headerText); Getting Started with Task Parallel Library 28 var words = client.DownloadString(uri); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); return wordArray.Count(); })); 5. Now we just need to write the result to the Console. Just add the following code to the end of your program: Console.WriteLine("Calling the lazy variable"); Console.WriteLine("Origin of species word count: {0}",lazyCount. Value.Result ); Console.WriteLine("Press to exit."); Console.ReadLine(); 6. All done. In Visual Studio 2012, press F5 to run the project. You should see the output as shown in the following screenshot: How it works… System.Lazy creates a thread safe Lazy initialization of an object. Lazy initialization is primarily used to improve performance and avoid computational overhead until necessary. You can pass a delegate (remember that System Threading Task is just a wrapper around a delegate) to the System.Lazy constructor, and as we have done in this recipe, you can use a lambda expression to specify a factory method for object creation. This keeps all of the initialization code in one place. Lazy initialization occurs the first time the System.Lazy.Value property is accessed. Chapter 1 29 Handling task exceptions using try/catch block Let's face it; sometimes things just go wrong with our code. Even with the simplified parallel programming model provided by the TPL, we still need to be able to handle our exceptions. Tasks use System.AggregateException to consolidate multiple failures into a single exception object. In this recipe, we will take a look at the simplest way to handle System. AggregateException in our tasks: the try/catch blocks. The try-catch statement consists of a try block followed by one of more catch blocks, which specify handlers for different exceptions. The try block contains the guarded code that may cause the exception. Getting ready… For this recipe we need to turn off the Visual Studio 2012 Exception Assistant. The Exception Assistant appears whenever a runtime exception is thrown, and intercepts the exception before it gets to our handler. 1. To turn off the Exception Assistant, go to the Debug menu and select Exceptions. 2. Uncheck the user-unhandled checkbox next to Common Language Runtime Exceptions. Getting Started with Task Parallel Library 30 How to do it… Let's return to our WordCount solution so we can see how to handle an AggregateException thrown by a parallel task. 1. Start a new project using the C# Console Application project template and assign WordCount6 as the Solution name. 2. Add the following using statements are at the top of your Program class: using System; using System.Linq; using System.Net; using System.Threading.Tasks; 3. For this recipe, we will just need a single task. The task will be very similar to our other word count tasks, but in this one we will simulate a problem with the System. Net.WebClient by creating and throwing a System.Net.WebException. In the Main method of your Program class, create System.Task that looks as the following Task: Task task1 = Task.Factory.StartNew(() => { const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; Console.WriteLine("Starting the task."); var client = new WebClient(); client.Headers.Add("user-agent", headerText); var words = client.DownloadString(@"http://www.gutenberg.org/ files/2009/2009.txt"); var ex = new WebException("Unable to download book contents"); throw ex; return 0; }); 4. Just below the Task, let's put in our try/catch blocks as shown in the following code snippet. In the catch block, we will want to specifically catch System. AggregateException. try { } catch (AggregateException aggEx) { } Chapter 1 31 5. Now let's implement the body of our try block. The body of the try block should be as shown in the following code snippet. There are a couple of subtle but important concepts in here that will be explained later in the chapter. try { task1.Wait(); if (!task1.IsFaulted) { Console.WriteLine("Task complete. Origin of Species word count: {0}",task1.Result); } } 6. Next, let's implement the body of our catch block. It should look as shown in the following code snippet: catch (AggregateException aggEx) { foreach (var ex in aggEx.InnerExceptions) { Console.WriteLine("Caught exception: {0}", ex.Message); } } 7. After the catch block, let's finish up by prompting the user to exit, and waiting on the user to hit Enter. Console.WriteLine("Press to exit."); Console.ReadLine(); 8. In Visual Studio 2012, press F5 to run the project. You should see output as shown in the following screenshot: Getting Started with Task Parallel Library 32 How it works… All of this stuff has been pretty self-explanatory so far, but handling exceptions in task involves a couple of subtleties that need to be pointed out. The task itself is pretty straightforward. Other than throwing the System.Net.WebException, there is nothing out of the ordinary here. Let's take a closer look at the try/catch blocks. The first statement in the try block System. Threading.Task.Wait() to wait on task completion. However, there is another purpose here. Unhandled exceptions thrown inside a task are swallowed by the runtime and wrapped up in System.AggregateException. It is your job to handle this. The TPL also has the concept of AggregateException being observed. If AggregateException is raised by your task, it will only be handled if it is currently being observed. This is very important to understand. If you never take an action that causes the exceptions to be observed, you are going to have a problem. When the Task object is garbage collected, the Finalize method of the task will see that the task had unobserved exceptions, and it will throwSystem.AggregateException. You will not be able to catch an exception thrown by the finalizer thread and your process will be terminated. So how to you observe an AggregateException, you ask? The Systm.Threading. Task class has a few methods and properties call triggers that cause System. AggregateException to be observed. A few of these are as follows: ff Task.Wait ff Task.WaitAny ff Task.WaitAll ff Task.Result Using any of these trigger methods indicates to the runtime that you are interested in observing any System.AggregateException that occurs. If you do not use one of the trigger methods on the Task class, the TPL will not raise any AggregateException, and an unhandled exception will occur. Now, let's take a look at the catch block. System.AggregateException can wrap many individual exception objects. In our catch block, we need to loop through AggregateException.InnerExceptions to take a look at all of the individual exceptions that occurred in a task. It is important to note that there is really no way to correlate an exception from the AggregateExcetion.InnerExceptions collection back to the particular task that threw an exception. All you really know is that some operation threw an Exception. Chapter 1 33 System.AggregateException overrides the GetBaseException method of exception, and returns the innermost exception, which is the initial cause of the problem. Handling task exceptions with AggregateException.Handle In this recipe, we will look at another way to handle System.AggregateException, by using the AggregateException.Handle method. The Handler method invokes a handler function for each exception wrapped in AggregateException. Getting ready… For this recipe, we need to turn off the Visual Studio 2012 Exception Assistant. The Exception Assistant appears whenever a runtime exception is thrown and intercepts the exception before it gets to our handler. 1. To turn off the Exception Assistant, go to the Debug menu and select Exceptions. 2. Uncheck the user-unhandled checkbox next to Common Language Runtime Exceptions. How to do it… Let's take a look at how we can use AggregateException.Handle to provide an alternate method to handling exceptions in a parallel application. 1. For this recipe, we will return to our WordCount6 project, and modify it to handle our exceptions in a different way. Start Visual Studio 2012 and open the WordCount6 project. 2. The first step is to define our handler function that will be invoked when we call AggregateException.Handle. Following the Main method of your Program class, add a new private static handler method that returns a bool. It should look as the following code snippet: private static bool HandleWebExceptions(Exception ex) { if (ex is WebException) { Console.WriteLine(("Caught WebException: {0}", ex.Message); return true; } Getting Started with Task Parallel Library 34 else { Console.WriteLine("Caught exception: {0}", ex.Message); return false; } } 3. The only other step here is to replace the body of your catch block with a call to System.AggregateException.Handle, passing in the HandleWebExceptions predicate. The updated try/catch block should look as follows: try { task1.Wait(); if (!task1.IsFaulted) { Console.WriteLine("Task complete. Origin of Species word count: {0}",task1.Result); } } catch (AggregateException aggEx) { aggEx.Handle(HandleWebExceptions); } 4. Those are the only modifications necessary. In Visual Studio 2012, press F5 to run the project. You should see output as shown in the following screenshot: Chapter 1 35 How it works… AggregateException.Handle() takes a predicate that you supply, and the predicate will be invoked once for every exception wrapped in System.AggregateException. The predicate itself just needs to contain the logic to handle the various exception types that you expect, and to return true or false to indicate whether the exception was handled. If any of the exceptions went unhandled, they will be wrapped in a new System. AggregateException and thrown. Cancelling a task Up to this point, we have focused on creating, running, and handling exceptions in tasks. Now we will begin to take a look at using System.Threading.CancellationTokenSource and System.Threading.CancellationToken to cancel tasks. This recipe will show how to cancel a single task. How to do it… Let's create a console application that shows how to cancel a parallel task. 1. Start a new project using the C# Console Application project template, and assign WordCount7 as the Solution name. 2. Add the following using statements at the top of your Program class: using System; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; 3. Let's start by creating CancellationTokenSource and getting our CancellationToken. In the Main method of your Program class, add the following statements: //Create a cancellation token source CancellationTokenSource tokenSource = new CancellationTokenSource(); //get the cancellation token CancellationToken token = tokenSource.Token; Getting Started with Task Parallel Library 36 4. Now we need to create our Task and pass CancellationToken into the constructor. Right after the previous line, put in the following Task definition: Task task1 = Task.Factory.StartNew(() => { // The body of the task goes here }, token); 5. In the body of our Task, we need to check the IsCancellationRequested property of CancellationToken. If it has been, we dispose of our resource and throw OperationCancelledException. If not, we do our usual work. Enter the following code into the body of Task: //wait a bit for the cancellation Thread.Sleep(2000); const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; var client = new WebClient(); client.Headers.Add("user-agent", headerText); if(token.IsCancellationRequested) { client.Dispose(); throw new OperationCanceledException(token); } else { var book = client.DownloadString(@"http://www.gutenberg.org/ files/2009/2009.txt"); char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' }; Console.WriteLine("Starting the task."); var wordArray = book.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); return wordArray.Count(); } 6. Right after the task, put in the following lines to write the cancellation status to the Console, and then call the Cancel method of TokenSource. Console.WriteLine("Has the task been cancelled?: {0}", task1. IsCanceled); //Cancel the token source tokenSource.Cancel(); 7. The following are the last statements put in a condition to check whether the task has been cancelled or faulted before we try to write out the results: Chapter 1 37 if (!task1.IsCanceled || !task1.IsFaulted) { try { if (!task1.IsFaulted) { Console.WriteLine("Origin of Specied word count: {0}", task1.Result); } } catch (AggregateException aggEx) { foreach (Exception ex in aggEx.InnerExceptions) { Console.WriteLine("Caught exception: {0}", ex.Message); } } } else { Console.WriteLine("The task has been cancelled"); } 8. Lastly, we'll finish up by prompting the user to exit and waiting for the input. Console.WriteLine("Press to exit."); Console.ReadLine(); 9. In Visual Studio 2012, press F5 to run the project. You should see the output as shown in the following screenshot: Getting Started with Task Parallel Library 38 How it works… The basic idea of cancelling task is that we create CancellationTokenSource, obtain CancellationToken from it, and then pass CancellationToken onto the Task constructor. Once we have done that, we can call the Cancel method on the CancellationTokenSource to cancel the task. That's all easy enough. However, inside task we have a couple of options on how to handle the cancellation. If your task has resources that need to be cleaned up (such as the WebClient), you need to check the cancellation tokens IsCancellationRequested property, then dispose of the resources, and throw a new OperationCancelledException. The other option, if your task doesn't use resources which need to be explicitly cleaned up, is to use the token ThrowIsCancellationRequested(), which will ensure the task transitions to a status of cancelled in a single statement. If you need to execute task and prevent it from being cancelled, you can obtain a special CancellationToken that is not associated with any CancellationTokenSource from the static CancellationToken.None property, and pass this token to Task. Since there is no associated CancellationTokenSource, it is not possible to call Cancel for this token, and any code that is checking the CancellationToken.IsCancellationRequested property will always get back a false. There's more… You can register one or more methods to be called when CancellationTokenSource is cancelled, by registering a callback method with the CancellationToken.Register method. You just need to pass Action, and optionally, a state value will be passed to callback and a Boolean, indicating whether to invoke the delegate using SynchronizationContext of the calling thread to the Register method. Passing in a value of false means the thread that calls Cancel will invoke the registered methods synchronously. If you pass true, the callbacks will be sent to SynchronizationContext, which determines which thread will invoke the callbacks. var source = new CancellationTokenSource(); source.Token.Register(()=> { Console.WriteLine("The operation has been cancelled."); }); Chapter 1 39 The Register method of CancellationToken returns a CancellationTokenRegistration object. To remove a registered callback from CancellationTokenSource, so it doesn't get invoked, call the Dispose method of the CancellationTokenRegistration object. You can also create CancellationTokenSource by linking other CancellationTokenSource objects together. The new composite CancellationTokenSource will be cancelled, if any of the linked CancellationTokenSource objects are cancelled. var source1 = new CancellationTokenSource(); var source2 = new CancellationTokenSource(); var linkedSource = CancellationTokenSource. CreateLinkedTokenSource(source1.Token, source2.Token); Cancelling one of many tasks Now that we have seen how to cancel a task, let's take a look at how we can use CancellationToken to cancel multiple tasks with a single call to CancellationTokenSource.Cancel(). How to do it… Now let's return to our WordCount example and create a Console application that provides for the cancellation of multiple tasks with single CancellationToken. 1. Start a new project using the C# Console Application project template, and assign WordCount8 as the Solution name. 2. Add the following using statements at the top of your Program class: using System; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; 3. First, let's create a helper method to display errors and cancellation status. Since we have multiple tasks, it's better to have this logic all in one place. Following the Main method of your Program class, create a static method call HandleExceptions which will display the errors and task status to the user. private static void DisplayException(Task task, AggregateException outerEx, string bookName) Getting Started with Task Parallel Library 40 { foreach (Exception innerEx in outerEx.InnerExceptions) { Console.WriteLine("Handled exception for {0}:{1}",bookName,innerEx.Message); } Console.WriteLine("Cancellation status for book {0}: {1}", bookName, task.IsCanceled); } 4. Next, at the top of the Main method, create CancellationTokenSource and get our CancellationToken. //Create a cancellation token source CancellationTokenSource tokenSource = new CancellationTokenSource(); //get the cancellation token CancellationToken token = tokenSource.Token; 5. Now we need to create our tasks and our array of delimiters. The tasks are the same as in the recipe for cancelling a single task. The key here is that we are passing the same CancellationToken in for all three tasks. char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\ u000A' }; const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; Task task1 = Task.Factory.StartNew(() => { // wait for the cancellation to happen Thread.Sleep(2000); var client = new WebClient(); client.Headers.Add("user-agent", headerText); if (token.IsCancellationRequested) { client.Dispose(); throw new OperationCanceledException(token); } else { var words = client.DownloadString(@"http://www.gutenberg. org/files/2009/2009.txt"); Console.WriteLine("Starting the task for Origin of Species."); Chapter 1 41 var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); return wordArray.Count(); } },token); Task task2 = Task.Factory.StartNew(() => { // wait for the cancellation to happen Thread.Sleep(2000); var client = new WebClient(); client.Headers.Add("user-agent", headerText); if (token.IsCancellationRequested) { client.Dispose(); throw new OperationCanceledException(token); } else { var words = client.DownloadString(@"http://www.gutenberg. org/files/16328/16328-8.txt"); Console.WriteLine("Starting the task for Beowulf."); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); return wordArray.Count(); }; },token); Task task3 = Task.Factory.StartNew(() => { // wait for the cancellation to happen Thread.Sleep(2000); var client = new WebClient(); client.Headers.Add("user-agent", headerText); if (token.IsCancellationRequested) { client.Dispose(); throw new OperationCanceledException(token); } else { Getting Started with Task Parallel Library 42 var words = client.DownloadString(@"http://www.gutenberg. org/files/4300/4300.txt"); Console.WriteLine("Starting the task for Ulysses."); var wordArray = words.Split(delimiters, StringSplitOptions. RemoveEmptyEntries); return wordArray.Count(); }; },token); 6. OK, let's finish up by calling CancellationTokenSource.Cancel() , checking the results, and catching the exceptions. The remainder of the Main method should look as the following code snippet: //Cancel the token source tokenSource.Cancel(); try { if (!task1.IsFaulted || !task1.IsCanceled) { Console.WriteLine("Origin of Specied word count: {0}", task1.Result); } } catch(AggregateException outerEx1) { DisplayException(task1, outerEx1, "Origin of Species"); } try { if (!task2.IsFaulted || !task2.IsCanceled) { Console.WriteLine("Beowulf word count: {0}", task2.Result); } } catch (AggregateException outerEx2) { DisplayException(task2, outerEx2, "Beowulf"); } try { if (!task3.IsFaulted || !task3.IsCanceled) { Console.WriteLine("Ulysses word count: {0}", task3.Result); } } catch (AggregateException outerEx3) { DisplayException(task3, outerEx3, "Ulysses"); Chapter 1 43 } Console.ReadLine(); 7. In Visual Studio 2012, press F5 to run the project. You should see output as shown in the following screenshot: How it works… Functionally, cancelling multiple tasks is the same as cancelling a single task. In fact, the Parallel Extensions team has put a lot of work into making cancellation of various parallel structures very similar, as you will see as we go through the book. All that is necessary to cancel multiple tasks is to create CancellationToken, then pass that token into all of the tasks you wish to cancel as shown in the following code snippet: CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; Task task1 = Task.Factory.StartNew(() => { ... },token); Task task, = Task.Factory.StartNew(() => { ... },token); tokenSource.Cancel(); 2 Implementing Continuations In this chapter, we will cover: ff Continuing a task ff Passing task results to a continuation ff Continue "WhenAny" and "WhenAll" ff Specifying when a continuation will run ff Using a continuation for exception handling ff Cancelling a continuation ff Using a continuation to chain multiple tasks ff Using a continuation to update a UI Introduction When you are writing an application that has tasks and that execute in parallel, it is common to have some parallel tasks that depend on the results of other tasks. These tasks should not be started until the earlier tasks, known as antecedents, have been completed. In fact, to write truly scalable software, you should not have threads that block. Calling Wait or querying Task.Result, when the task has not finished running, will cause your threads to block. Fortunately, there is a better way. Implementing Continuations 46 Prior to the introduction of the Task Parallel Library (TPL), this type of interdependent thread execution was done with callbacks, where a method was called, and one of its parameters was a delegate to execute when the task completed. This provided a viable solution to the dependency problems but quickly became very complex in the real-world application. This is especially true if, for example, you had a task that needed to run after several other tasks had completed. With the TPL, a simpler solution exists in the form of continuation tasks. These tasks are linked to their antecedents, and are automatically started after the earlier tasks have been completed. What makes continuations so powerful is that, you can create continuations that run when a task or a group of tasks completes throws an exception, or gets cancelled. As you will see in this chapter, continuations can even provide a means to synchronize the asynchronous method results with the user interface running on another thread. We will start the chapter with a basic, simple continuation that runs when a single task completes. From there, we will look at using continuations to control a collection of tasks, using continuations to handle exceptions, and using continuations to chain multiple tasks together. We will finish the chapter by creating a Windows Presentation Foundation (WPF) application, using a continuation to marshal data created in a task back to the user interface. Continuing a task In its simplest form, a continuation is an action that runs asynchronously after a target task, called an antecedent, completes. In the first recipe of this chapter, we will build a basic continuation. We will accomplish this by using the Task.ContinueWith(Action) method. How to do it… Let's go to Visual Studio and create a console application that runs a task continuation after our word count task completes. The steps to create a console application are as follows: 1. Start a new project using the C# Console Application project template and assign Continuation1 as the Solution name. 2. Add the following using directives to the top of your program class: using System; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; Chapter 2 47 3. Now let's put a try/catch block and some basic exception handling. The Main method of the program class, at this point, should look as shown in the following code snippet: static void Main() { try { // The Task and Continuation will go here } catch (AggregateException aEx) { foreach (Exception ex in aEx.InnerExceptions) { Console.WriteLine("An exception has occured: {0}" + ex.Message); } } } 4. Inside the try block, create a WebClient object and set the user-agent header as shown in the following code snippet: var client = new WebClient(); const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; client.Headers.Add("user-agent", headerText); 5. Next, in the body of the try block, let's create an anonymous Task (no name), followed by a .ContinueWith() right after the closing parenthesis of the Task. The antecedent Task doesn't return any results in this recipe. Task.Factory.StartNew(() => { }).ContinueWith(obj => { }).Wait(); 6. Finally, we need to create the body of the Task and the continuation. The Task will execute one of our familiar word counts. The continuation will be used to clean up the reference to the WebClient object after the antecedent task completes. After the continuation, prompt the user to exit. Task.Factory.StartNew(() => { Console.WriteLine("Antecedent running."); Implementing Continuations 48 char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' }; var words = client.DownloadString(@"http://www.gutenberg.org/files/2009 /2009.txt"); var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); Console.WriteLine("Word count for Origin of Species: {0}", wordArray.Count()); } ).ContinueWith(antecedent => { Console.WriteLine("Continuation running"); client.Dispose(); }).Wait(); 7. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following screenshot: How it works… There isn't a lot to explain about this basic continuation, but there are a couple of small points to note. Chapter 2 49 For this recipe, we created an anonymous Task and made the call to ContinueWith right after the closing parenthesis of the task as follows: Task.Factory.StartNew(() => { }).ContinueWith(obj => { }).Wait(); We could just as well have created a named task and made the call to ContinueWith in a separate statement shown as follows: Task task1 = Task.Factory.StartNew(() => { }); task1.ContinueWith(obj => { }).Wait(); Also, notice that we can wait for a continuation using the Wait() method; in the same way we could wait for a Task (however, you will not normally do this in practice. It causes the thread to block waiting for the continuation to complete. In general, you want to avoid causing your threads to block). In fact, tasks and continuations aren't much different and have many of the same instance methods and properties. Passing task results to a continuation In this recipe, we will see how we can pass the results returned from an antecedent Task to a continuation. Our antecedent Task is going to read in the contents of a book as a string and display a word count to the user. The continuation, which will run after the antecedent completes, will take the string array returned by the antecedent and perform a LINQ query which will find the five most frequently used words. How to do it… Let's start Visual Studio and build a Console Application that shows how to pass results from the antecedent to a continuation. The steps are given as follows: 1. Start a new project using the C# Console Application project template and assign Continuation2 as the Solution name. Implementing Continuations 50 2. Add the following using directives to the top of your program class: using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; 3. To begin with, let's put some basic stuff in the class. We will need a character array of delimiters so that we can parse out the words properly. Also, we need a try/catch block and some basic exception handling. The Main method of the Program class, at this point, should look as follows: static void Main() { char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' }; try { // The Task and Continuation will go here } catch (AggregateException aEx) { foreach (Exception ex in aEx.InnerExceptions) { Console.WriteLine("An exception has occured: {0}" + ex.Message); } } } 4. Now let's create a task called task1 that returns an array of strings as its result. The purpose of task1 will be to create System.Net.WebClient which will read in the text of the book as a string. Once the string is parsed and put into a string array, we will display the word count to the user by using the Count method of the array, and then return the array in the tasks result so that it can be used in our continuation. Create the task inside the try block. The body of the try block should now look something like the following code: try { Task task1 = Task.Factory.StartNew(() => { var client = new WebClient(); const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; Chapter 2 51 client.Headers.Add("user-agent",headerText); var words = client.DownloadString(@"http://www.gutenberg.org/files/2009 /2009.txt"); string[] wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); Console.WriteLine("Word count for Origin of Species: {0}", wordArray.Count()); Console.WriteLine(); return wordArray; } } 5. Next, we are going to create our continuation using the Task.ContinueWith() method. Our continuation will have a Task state parameter. The body of the continuation will perform a Linq query on the string array to sort all of the words contained in the array by the number of times the words occur. We will then execute another Linq operation to take the top five most frequently used words and write them to the console. Finally, we will want to wait on the continuation to complete with the Wait() method. Create the task continuation right after the body of the antecedent task. task1.ContinueWith(antecedent => { var wordsByUsage = antecedent.Result.Where(word => word.Length > 5) .GroupBy(word => word) .OrderByDescending(grouping => grouping.Count()) .Select(grouping => grouping.Key); var commonWords = (wordsByUsage.Take(5)).ToArray(); Console.WriteLine("The 5 most commonly used words in Origin of Species:"); Console.WriteLine("------------------------------------ ----------------"); foreach (var word in commonWords) { Console.WriteLine(word); } }).Wait(); 6. OK, the last step for this recipe is to let the user know that our application is finished and prompt them to exit. Put that code right after the continuation. It should be the last lines in the try block. Console.WriteLine(); Console.WriteLine("Complete. Please hit to exit."); Console.ReadLine(); Implementing Continuations 52 7. In Visual Studio 2012, press F5 to run the project. You should see the output similar to the following screenshot: How it works… The continuation in this recipe was created using the ContinueWith method of an existing task instance as we did in the previous recipe. In this recipe however, we use a Lambda expression to pass in a Task parameter representing the antecedent Task. Task task1 = Task.Factory.StartNew(() => { //Task Action }); task1.ContinueWith(antecedent => { //Continuation Action }); Notice that the continuation accesses the result of the antecedent using the Task.Result property. If this looks familiar, it should. You access the results of a task in nearly the same way in a continuation as you would in any piece of your code, that is, by accessing the Result property of a Task. The Parallel Extensions team has made the coding experience very consistent across all parallel operations. task1.ContinueWith(antecedent => { Chapter 2 53 var wordsByUsage = antecedent.Result.Where(word => word.Length > 5) .GroupBy(word => word) .OrderByDescending(grouping => grouping.Count()) .Select(grouping => grouping.Key); var commonWords = (wordsByUsage.Take(5)).ToArray(); Console.WriteLine("The 5 most commonly used words in Origin of Species:"); Console.WriteLine("--------------------------------------------- -------"); foreach (var word in commonWords) { Console.WriteLine(word); } }); Lastly, we wait for the continuation to complete before prompting the user to exit. Continue "WhenAny" and "WhenAll" In this recipe we will move from continuing single tasks to setting up continuations for groups of tasks. The two methods we will be looking at are WhenAny and WhenAll. Both methods are static members of the Task.Factory class, and take an array of tasks and Action as their parameters. First we will look at the WhenAny continuations. The basic idea here is that we have a group of tasks and we only want to wait for the first and fastest of the group to complete its work before moving on. In our case, we will be downloading the text of three different books, and performing a word count on each. When the first task completes we will display the word count of the winner to the user. After that we will change to WhenAll and display the results of all three word counts to the user. How to do it… Let's build a solution that shows how to conditionally continue a task. The steps are as follows: 1. Start a new project using the C# Console Application project template and assign Continuation3 as the Solution name. 2. Add the following using directives to the top of your program class: using System; using System.Collections.Generic; Implementing Continuations 54 using System.Linq; using System.Net; using System.Threading.Tasks; 3. First, in the Main method of your program class, let's create a character array of delimiters we can use to split our words with, a string constant for the user agent header of our web client, and a Dictionary method to hold our book titles and URLs. The dictionary will serve as the state object parameter for our tasks, which will be created in a foreach loop. char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' }; const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; var dictionary = new Dictionary { {"Origin of Species", "http://www.gutenberg.org/files/2009/2009.txt"}, {"Beowulf", "http://www.gutenberg.org/files/16328/16328-8.txt"}, {"Ulysses", "http://www.gutenberg.org/files/4300/4300.txt"} }; 4. Next, let's create a try/catch block with some basic error handling. try { // Loop to create and Continuation will go here } catch (AggregateException aEx) { foreach (Exception ex in aEx.InnerExceptions) { Console.WriteLine("An exception has occured: {0}" + ex.Message); } } 5. Inside the try block, let's create a new list of Task>. Of course, this will be the list of our tasks. Each task will take a KeyValuePair from the dictionary we created in step 3 as their state parameters. var tasks = new List>>(); Chapter 2 55 6. Now let's create our task in a foreach loop. Each task will read the text of a book from a string, split the string into a character array, and do a word count. Our antecedent tasks return a KeyValuePair with the book title and the word count for each book. foreach (var pair in dictionary) { tasks.Add(Task.Factory.StartNew(stateObj => { var taskData = (KeyValuePair)stateObj; Console.WriteLine("Starting task for {0}", taskData.Key); var client = new WebClient(); client.Headers.Add("user-agent", headerText); var words = client.DownloadString(taskData.Value); var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); return new KeyValuePair(taskData.Key, wordArray.Count()); }, pair)); } 7. Now let's create the continuation by calling the Task.Factory.WhenAny method. The continuations will just display the title and word count of the winner to the user. Task.Factory.ContinueWhenAny(tasks.ToArray(), antecedent => { Console.WriteLine("And the winner is: {0}", antecedent.Result. Key); Console.WriteLine("Word count: {0}", antecedent.Result.Value); }).Wait(); 8. Lastly, after the catch block, prompt the user to exit and wait for the input. Console.WriteLine("Complete. Press to exit."); Console.ReadLine(); Implementing Continuations 56 9. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following. Your winner may vary. 10. Before moving on, let's change our code a bit and continue when all of our tasks complete. All we need to do is change our method call from Task.Factory. WhenAny to Task.Factory.WhenAll, change the name of the continuation parameter from antecedent to antecedents to reflect plurality, and create a foreach loop in the body of the continuation to loop through the results. Task.Factory.ContinueWhenAll(tasks.ToArray(), antecedents => { foreach (var antecedent in antecedents) { Console.WriteLine("Book Title: {0}", antecedent.Result. Key); Console.WriteLine("Word count: {0}", antecedent.Result. Value); } }).Wait(); 11. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following screenshot: Chapter 2 57 How it works… The continuations in this recipe are created a bit differently from the continuations that we have created in previous tasks. Instead of calling the instance method ContinueWith on a Task variable, we are calling the ContinueWhenAny and ContinueWhenAll static methods on Task.FactoryClass. Task.Factory.ContinueWhenAll(tasks.ToArray(), antecedents => { }); The ContinueWhenAny and ContinueWhenAll methods have a different parameter lists than Task.ContinueWith. ContinueWhenAny takes an array of Task as its first parameter and a single Action delegate as its second parameter. ContinueWhenAny(Task[], Action) ContinueWhenAll takes the same array of Task as its first parameter and Action as its second parameter. ContinueWhenAll(Task[], Action) Implementing Continuations 58 Specifying when a continuation will run One of the most powerful features of task continuations is the ability to create multiple continuations for a task, and specify the exact conditions under which each continuation will be invoked by using the Task.TaskContinuationOptions enumeration. When you create a continuation for a task, you can use Task.ContinueWith overload that takes the TaskContinuationOptions enumeration to specify that the continuation will only run if the antecedent Task completed, was cancelled, or is faulted. The enumeration also has members that specify when a continuation should not run. In this recipe, we will be looking at two simple tasks, each with two continuations. One of the continuations for each task will run when the task completes, and one will run when the task is cancelled. How to do it… Now, let's create a console application that continues tasks conditionally. The steps to create a console application are as follows: 1. Start a new project using the C# Console Application project template and assign Continuation4 as the Solution name. 2. Add the following using directives to the top of your program class: using System; using System.Threading; using System.Threading.Tasks; 3. At the top of the Main method, create two CancellationTokenSource objects and get a CancellationToken from each one of them. var tokenSource1 = new CancellationTokenSource(); var token1 = tokenSource1.Token; var tokenSource2 = new CancellationTokenSource(); var token2 = tokenSource2.Token; 4. Next, let's create a try/catch block with some basic error handling. try { // Tasks and Continuations will go here } catch (AggregateException aEx) Chapter 2 59 { foreach (Exception ex in aEx.InnerExceptions) { Console.WriteLine("An exception has occured: {0}" + ex.Message); } } 5. Inside the try block, let's create two simple tasks. Both tasks just write a message to the console. Also create two continuations for each task using TaskContinuationOptions.OnlyOnRanToCompletion and Task ContinuationOption.OnlyOnFaulted. var task1 = Task.Factory.StartNew(() => { Console.WriteLine("Task #1 is running."); //wait a bit Thread.Sleep(2000); }, token1); task1.ContinueWith(antecedent => Console.WriteLine("Task #1 completion continuation."), TaskContinuationOptions.OnlyOnRanToCompletion); task1.ContinueWith(antecedent => Console.WriteLine("Task #1 cancellation continuation."), TaskContinuationOptions.OnlyOnCanceled); var task2 = Task.Factory.StartNew(() => { Console.WriteLine("Task #2 is running."); //wait a bit Thread.Sleep(2000); }, token2); task2.ContinueWith(antecedent => Console.WriteLine("Task #2 completion continuation."), TaskContinuationOptions.OnlyOnRanToCompletion); task2.ContinueWith(antecedent => Console.WriteLine("Task #2 cancellation continuation."), TaskContinuationOptions.OnlyOnCanceled); 6. Lastly, after the catch block, let's cancel the token and wait for user input before exiting. tokenSource2.Cancel(); Console.ReadLine(); Implementing Continuations 60 7. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following screenshot: How it works… In this very simple example, we started by creating two CancellationTokenSource objects and getting a cancellation token source from each. If we had created a CancellationTokenSource object and passed the token into both tasks, both tasks would have been cancelled when we cancelled the token. In our case, we just wanted to cancel one of the two tasks. The tasks themselves are very simple. They just wait for a bit to give us some time to cancel the token and display a message to the console. We pass one CancellationToken into each task as shown in the following code snippet: var task1 = Task.Factory.StartNew(() => { Console.WriteLine("Task #1 is running."); //wait a bit Thread.Sleep(2000); }, token1); var task2 = Task.Factory.StartNew(() => { Console.WriteLine("Task #2 is running."); Chapter 2 61 //wait a bit Task2.Delay(2000); }, token2); Both of the continuations just display a message to the console, and both are created with a member of the Task.TaskContinuationOptions enumerator. The first continuation is fired when the task runs to completion and the second continuation fires when the task is cancelled. task1.ContinueWith(antecedent => Console.WriteLine("Task #1 completion continuation."), TaskContinuationOptions.OnlyOnRanToCompletion); task1.ContinueWith(antecedent => Console.WriteLine("Task #1 cancellation continuation."), TaskContinuationOptions.OnlyOnCanceled); We cancel the token for task2, but not for task1 and the corresponding continuation for each executes, and we can see the message written on to the console. There's more… The TaskContinuationOptions enumeration has several members which control under which condition a continuation is triggered. The following table contains a list of these members. Note that this is not a complete list of continuation options. The complete list of continuation options can be found at http://msdn.microsoft.com/en-us/library/ system.threading.tasks.taskcontinuationoptions.aspx. The OnlyOnFaulted member will have its own recipe later in the chapter. NotOnRanToCompletion The continuation should not be scheduled if the task ran to completion. NotOnFaulted The continuation should not be scheduled if the task faulted. NotOnCancelled The continuation should not be triggered if the task was cancelled. OnlyOnRanToCompletion The continuation should be scheduled if the task ran to completion. OnlyOnFaulted The continuation should be scheduled if the task faulted. OnlyOnCancelled The continuation should be triggered if the task was cancelled. The TaskContinuationOptions enumeration can be treated as a bit field and a bitwise combination can be performed on its members. Implementing Continuations 62 Using a continuation for exception handling In the Handling task exceptions using try/catch recipe, in Chapter 1, Getting Started With Task Parallel Library we looked at how to handle exceptions in task. In addition to the techniques used in that recipe, you can also use continuations to handle task exceptions. By using a continuation, we can handle errors in a cleaner, less inline way. An exception handling continuation allows for centralizing exception handling logic in cases where you would want to provide logging or other exception related code. The basic concept is to use the Task.TaskContinuationOptions enumeration so we can create a continuation that will be scheduled if the task ran to completion, and another continuation that will be scheduled if the task is put into a faulted state. Getting Ready For this recipe we need to turn off the Visual Studio 2012 Exception Assistant. The Exception Assistant appears whenever a run-time exception is thrown and intercepts the exception before it gets to our handler. 1. To turn off the Exception Assistant, go to the Debug menu and select Exceptions. 2. Uncheck the User-unhandled checkbox next to Common Language Runtime Exceptions. Chapter 2 63 How to do it… Now, let's go to Visual Studio and see how to use a continuation for exception handling. The steps are given as follows: 1. Start a new project using the C# Console Application project template and assign Continuation5 as the Solution name. 2. Add the following using directives at the top of your program class: using System; using System.Threading; using System.Threading.Tasks; 3. In the Main method of your program class, create a Task. The task doesn't need to accept a state parameter or return anything. In the body of the Task, create the try/ finally blocks. In order to have a resource to dispose of, create a new WebClient in the try block, and then throw an exception. In the finally block, call the dispose method of the WebClient. Other than that, the exact details don't matter much. Task task1 = Task.Factory.StartNew(() => { Console.WriteLine("Starting the task."); var client = new WebClient(); const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; client.Headers.Add("user-agent", headerText); try { var book = client.DownloadString(@"http://www.gutenberg. org/files/2009 /2009.txt"); var ex = new WebException("Unable to download book contents"); throw ex; } finally { client.Dispose(); Console.WriteLine("WebClient disposed."); } }); Implementing Continuations 64 4. Immediately following the Task, use TaskContinuationOptions. OnlyOnRanToCompletion to create a trivial continuation to run when the task completes successfully. This continuation only needs to write a message to the console. task1.ContinueWith(antecedent=> { Console.WriteLine("The task ran to completion."), }, TaskContinuationOptions.OnlyOnRanToCompletion); 5. Next use TaskContinuationOptions.OnlyOnFaulted to create a continuation that only runs when task1 throws a fault. After the continuation, add Console.Readline to wait for user input before exiting. task1.ContinueWith(antecedent => { Console.WriteLine("The task faulted."); var aEx = antecedent.Exception; if (aEx != null) foreach (var ex in aEx.InnerExceptions) { Console.WriteLine("Handled Exception: {0}",ex.Message); } }, TaskContinuationOptions.OnlyOnFaulted); Console.ReadLine(); 6. In Visual Studio 2012, press F5 to run the project. You should see output similar to the one shown in the following screenshot: Chapter 2 65 How it works… Creating a continuation that will run when a Task is in a faulted state, works the same as setting any of the other enumerations in TaskContinuationOptions on a continuation. In order to properly clean up resources used by the Task, we created try/finally blocks in our task and disposed of the WebClient in the finally block: finally { client.Dispose(); Console.WriteLine("WebClient disposed."); } Our exception handling continuation checks to see if the AggregateException is null before looping through the InnerExceptions collection, and writing the result to the console. The null check isn't strictly necessary because the antecedent task needs to be in a faulted state before the continuation is scheduled, but it is a good defensive coding practice none the less: task1.ContinueWith(antecedent => { Console.WriteLine("The task faulted."); var aEx = antecedent.Exception; if (aEx != null) foreach (var ex in aEx.InnerExceptions) { Console.WriteLine("Handled Exception: {0}",ex.Message); } }, TaskContinuationOptions.OnlyOnFaulted); Console.ReadLine(); Cancelling a continuation Cancelling a continuation follows the same basic rules as cancelling a Task. If a Task and its continuation are two parts of the same operation, you can pass the same cancellation token to both the Task and the continuation. In this recipe we will have a simple Task that creates a list of numbers and a continuation that squares the numbers and return a result. After a few seconds of running, we will use the token to cancel both the Task and the continuation. Implementing Continuations 66 Getting Ready Since cancelling a Task or continuation raises and OperationCanceledException we need to turn off the Visual Studio 2012 Exception Assistant. The Exception Assistant appears whenever a runtime exception is thrown, and intercepts the exception before it gets to our handler. 1. To turn off the Exception Assistant, go to the Debug menu and select Exceptions. 2. Uncheck the User-unhandled checkbox next to Common Language Runtime Exceptions. How to do it… Now, let's build a console application so that we can see how to cancel a continuation. The steps are as follows: 1. Start a new project using the C# Console Application project template and assign Continuation6 as the Solution name. 2. Add the following using directives to the top of your program class. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; Chapter 2 67 3. In the Main method of your program class, let's start by creating our CancellationTokenSource and getting a token. We will pass this token to both the antecedent Task and the continuation. var tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; 4. Next let's add try/catch/finally blocks to the Main method, just under the previous lines. Add some basic error handling to the catch block and dispose of the CancellationTokenSource in the finally block. try { //Task and Continuation go here } catch (AggregateException aEx) { foreach (var ex in aEx.InnerExceptions) { Console.WriteLine("An exception has occured: " + ex.Message); } } finally { tokenSource.Dispose(); } 5. Inside the try block, create a task that accepts an object state parameter. The parameter will determine the size of our number list. We will cast it to Int32 and create a for loop to add numbers to our list. Also, pass the token created in step 1 to the task constructor. var task1 = Task.Factory.StartNew(state => { Console.WriteLine("Task has started."); var result = new List(); for (var i = 0; i < (Int32) state; i++) { token.ThrowIfCancellationRequested(); result.Add(i); Thread.Sleep(100); //sleep to simulate some work Implementing Continuations 68 } return result; }, 5000,token); 6. After the Task, let's create our continuation. The continuation will receive the results from the antecedent Task, loop through the list, and square the numbers. Pass the same CancellationToken into the continuations constructor. task1.ContinueWith(antecedent => { Console.WriteLine("Continuation has started."); var antecedentResult = antecedent.Result; var squares = new List(); foreach (var value in antecedentResult) { token.ThrowIfCancellationRequested(); squares.Add(value*value); Thread.Sleep(100);//sleep to simulate some more work } return squares; },token); 7. At the end of the try block, we need to sleep the thread a bit to give the Task and continuation some time to run, and then we will cancel the token. Finally we will call the Wait method on task1. Thread.Sleep(2000); //wait for 2 seconds tokenSource.Cancel(); task1.Wait(); 8. Last, after the end of the finally block, write a message to the console that we are finished and wait for the user input. Console.WriteLine("Complete. Press enter to exit."); Console.ReadLine(); 9. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following screenshot: Chapter 2 69 How it works… When an antecedent task throws an OperationCancelledException in response to a cancellation request, as long as the continuation uses the same CancellationToken, the cancellation request will be treated as an acknowledgement of co-operative cancellation and both the antecedent task and the continuation will go into a cancelled state. This is pretty easy to accomplish. We just need to get a CancellationToken from a CancellationTokenSource, and pass the token to the constructors for both the antecedent Task and the continuation. var tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; var task1 = Task.Factory.StartNew(state => { // Task body }, 5000,token); task1.ContinueWith(antecedent => { //Continuation body },token); Implementing Continuations 70 Inside the body of the loops in our Task and the continuation, we need to poll for cancellation and throw an OperationCancelledException if the token gets cancelled. This can be done in one line of code with the ThrowIfCancellationRequested method of the CancellationToken object. foreach (var value in antecedentResult) { token.ThrowIfCancellationRequested(); squares.Add(value*value); Thread.Sleep(100);//sleep to simulate some more work } Lastly, we just need to make sure we are handling AggregateExceptions in our catch block. Using a continuation to chain multiple tasks Another feature of continuations is that you can continue continuations in order to chain tasks together to any length. The pipeline pattern can be implemented with a series of tasks and continuations. You can think of a pipeline as an assembly line in a factory. At the frontend of a pipeline, a producer task generates the data to be operated on, and each of the chained consumer stages operates on or changes the produced data. In this recipe we will return to our word count example to create a simple three stage pipeline using continuations with TaskContinuationOptions.OnlyOnRanToCompletion. How to do it… Open up Visual Studio, and let's see how to chain tasks together into a pipeline. The steps are as follows: 1. Start a new project using the C# Console Application project template and assign Continuation7 as the Solution name. 2. Add the following using directives to the top of your program class: using System; using System.Linq; using System.Net; using System.Threading.Tasks; 3. Let's start this application by adding try/catch blocks in the Main method of the program class. In the catch block add some handling for any AggregateException raised by the tasks. At the end of the catch block, write a message to the console to tell the user we are finished and wait for input to exit. Chapter 2 71 try { //Task and continuations go here } catch (AggregateException aEx) { foreach (var ex in aEx.InnerExceptions) { Console.WriteLine("An exception has occured: {0}", ex.Message); } } Console.WriteLine(); Console.WriteLine("Complete. Please hit to exit."); Console.ReadLine(); 4. Now we need to create a producer task that reads in the text of a book, and returns a string array, which the consumer continuations will consume. var producer = Task.Factory.StartNew(() => { char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' }; var client = new WebClient(); const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; client.Headers.Add("user-agent", headerText); try { var words = client.DownloadString(@"http://www.gutenberg.org/files/2009 /2009.txt"); var wordArray = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); Console.WriteLine("Word count for Origin of Species: {0}", wordArray.Count()); Console.WriteLine(); return wordArray; } finally { client.Dispose(); } }); Implementing Continuations 72 5. The first consumer will perform a Linq query on the results of the producer to find the five most commonly used words. Task consumer1 = producer.ContinueWith(antecedent => { var wordsByUsage = antecedent.Result.Where(word => word.Length > 5) .GroupBy(word => word) .OrderByDescending(grouping => grouping.Count()) .Select(grouping => grouping.Key); var commonWords = (wordsByUsage.Take(5)).ToArray(); Console.WriteLine("The 5 most commonly used words in Origin of Species:"); Console.WriteLine("------------------------------------ ----------------"); foreach (var word in commonWords) { Console.WriteLine(word); } Console.WriteLine(); return antecedent.Result; }, TaskContinuationOptions.OnlyOnRanToCompletion); The second consumer will perform another Linq query to find the longest word used. Task consumer2 = consumer1.ContinueWith(antecedent => { var longestWord = (antecedent.Result.OrderByDescending(w => w.Length)).First(); Console.WriteLine("The longest word is: {0}", longestWord); }, TaskContinuationOptions.OnlyOnRanToCompletion); consumer2.Wait(); 6. In Visual Studio 2012, press F5 to run the project. You should see output similar to the following screenshot: Chapter 2 73 How it works… The task and continuations we used in this example are pretty much the same as the tasks we have created in other recipes. The primary difference is how we chained them together and the length of the chain. Our antecedent task produces and returns a string array, and then we have a continuation that finds the five most commonly used words, finally we continue the continuation to find the longest word. Note that we also use TaskContinuationOptions.OnlyOnRanToCompletion because we only want the consumers to be scheduled to run when the previous task succeeded. To be a more complete solution, we would want to use TaskContinuationOptions. OnlyOnFaulted to set up a continuation for the failure path as well. Using a continuation to update a UI A common challenge when developing multithreaded WPF applications is that the UI controls have thread affinity, meaning they can only be updated by the thread that created them. This is usually the main thread of the application. The TPL, however, offers a clean way to marshal the results from a TPL task to the correct thread for updating the UI. It accomplishes this with the TaskScheduler. FromCurrentSynchronizationContext method which creates a TaskScheduler associated with the current SyncronizationContext. Implementing Continuations 74 In this recipe we are going to create a WPF application which will start a task to get the word count of a book. The task will have a continuation that is created in the correct synchronization context by calling TaskScheduler.FromCurrentSynchronizationContext. The continuation will perform the UI update. How to do it… Let's create a WPF application and see how we can use the TPL marshal data to the UI thread. 1. Start a new project using the WPF Application project template and assign Continuation8 as the Solution name. 2. Open the MainWindow.xaml.cs file and ensure the following using directives to the top of your MainWindow class: using System; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Windows; 3. Go back to MainWindow.xaml and replace the XAML with the following code to create the UI layout: