Improving performance of async solution with Task.WhenAll

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.

How to do it…

  1. Start a new project using the WPF Application project template and assign AsyncMultipleRequest as Solution name.
  2. Begin by opening 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>
  3. Next, open up MainWindow.xaml.cs. Go to the Project Explorer, and add a reference to System.Net.Http.
  4. Add the following 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;
  5. At the top of the 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' };
  6. Let's start by creating a helper function that builds a list of 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;
    }
  7. Now let's create a 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());
    }
  8. Next, we need to create the 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);
      }
    }
  9. Lastly, the start button click event handler just needs to call the 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;            
    }
  10. In Visual Studio 2012, press F5 to run the project. Your application should have results as shown in the following screenshot:
    How to do it…

How it works…

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);
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.83.240