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<Task>
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.
Let's build a solution that shows how to conditionally continue a task. The steps are as follows:
Continuation3
as the Solution name.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;
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<string, string>
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<string, string> { {"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"} };
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); } }
try
block, let's create a new list of Task<KeyValuePair<string, string>>
. 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<Task<KeyValuePair<string, int>>>();
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<string, int>
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<string, string>)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<string, int>(taskData.Key, wordArray.Count()); }, pair)); }
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();
Console.WriteLine("Complete. Press <Enter> to exit."); Console.ReadLine();
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();
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<Task>
delegate as its second parameter.
ContinueWhenAny(Task[], Action<Task>)
ContinueWhenAll
takes the same array of Task
as its first parameter and Action<Task[]>
as its second parameter.
ContinueWhenAll(Task[], Action<Task[]>)
18.223.196.171