We have already seen how we can use the Task.WhenAny
method to handle asynchronous tasks as they complete. You will also find the Task.WhenAll
method very useful in the asynchronous context. In some applications that create multiple asynchronous requests, it can improve application performance by using Task.WhenAll
to hold off on processing results until all the asynchronous tasks have completed.
In this recipe, we are going to create a WPF application that downloads the contents of multiple books asynchronously, but holds off on processing the results until all the tasks have completed.
AsyncMultipleRequest
as Solution name.MainWindow.xaml
and adding the following XAML to create our user interface:<Window x:Class="AsyncMultipleRequest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Button x:Name="StartButton" Content="Start Download" HorizontalAlignment="Left" Margin="194,264,0,0" VerticalAlignment="Top" Width="125" RenderTransformOrigin="-0.2,0.45" Click="StartButton_Click"/> <TextBlock x:Name="TextResult" HorizontalAlignment="Left" Margin="48,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="213" Width="420"/> </Grid> </Window>
MainWindow.xaml.cs
. Go to the Project Explorer, and add a reference to System.Net.Http
.using
directives to the top of your MainWindow
class:using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Documents; using System.Net.Http;
MainWindow
class, add a character
array constant that will be used to split the contents of the book into a word array.char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', 'u000A' };
KeyValuePair<string,string>
, which represents our book titles and URLs.private List<KeyValuePair<string, string>> GetBookUrls() { var urlList = new List<KeyValuePair<string, string>> { new KeyValuePair<string,string>("Origin of Species", "http://www.gutenberg.org/files/2009/2009.txt"), new KeyValuePair<string,string>("Beowulf", "http://www.gutenberg.org/files/16328/16328-8.txt"), new KeyValuePair<string,string>("Ulysses", "http://www.gutenberg.org/files/4300/4300.txt") }; return urlList; }
async
method that performs the book download and returns KeyValuePair<string, int>
that represents our book titles and word count. This method will need to accept a KeyValuePair<string, string>
parameter representing the book title and URL. The method also needs a HttpClient
parameter.async Task<KeyValuePair<string,int>> ProcessBook(KeyValuePair<string,string> book, HttpClient client) { var bookContents = await client.GetStringAsync(book.Value); var wordArray = bookContents.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); return new KeyValuePair<string,int>(book.Key,wordArray.Count()); }
GetWordCount
method. This method will execute a LINQ query to call the ProcessBook
method on each book
in the list of books. It then calls Task.WhenAll
to await
the tasks completed of all of the tasks. When all tasks have finished, it needs to write the results to the TextBlock
in a for
loop.public async Task GetWordCount() { var urlList = GetBookUrls(); var wordCountQuery = from book in urlList select ProcessBook(book); Task<KeyValuePair<string,int>>[] wordCountTasks = wordCountQuery.ToArray(); KeyValuePair<string, int>[] wordCounts = await Task.WhenAll(wordCountTasks); foreach (var book in wordCounts) { TextResult.Text += String.Format("Finished processing {0} : Word count {1} ", book.Key, book.Value); } }
GetWordCount
method and await
the task.private async void StartButton_Click(object sender, RoutedEventArgs e) { TextResult.Text = "Started downloading books... "; Task countTask = GetWordCount(); await countTask; }
In this recipe, the GetWordCount
method calls the ProcessBook
method for each book in the list by executing a LINQ query. This returns an IEnumerable<Task<TResult>>
, when we turn in to an array of tasks by calling the ToArray
method.
var urlList = GetBookUrls(); var wordCountQuery = from book in urlList select ProcessBook(book); var wordCountTasks = wordCountQuery.ToArray();
Next, we just await
a call to the Task.WhenAll
method which will return when all of the asynchronous tasks complete. Finally, we just use a for
loop to update the TextBlock
.
var wordCounts = await Task.WhenAll(wordCountTasks); foreach (var book in wordCounts) { TextResult.Text += String.Format("Finished processing {0} : Word count {1} ", book.Key, book.Value); }
18.216.83.240