Export (0) Print
Expand All

How to: Extend the Async Walkthrough by Using Task.WhenAll (C# and Visual Basic)

You can improve the performance of the async solution in Walkthrough: Accessing the Web by Using Async and Await (C# and Visual Basic) by using the Task.WhenAll method. This method asynchronously awaits multiple asynchronous operations, which are represented as a collection of tasks.

You might have noticed in the walkthrough that the websites download at different rates. Sometimes one of the websites is very slow, which delays all the remaining downloads. When you run the asynchronous solutions that you build in the walkthrough, you can end the program easily if you don't want to wait, but a better option would be to start all the downloads at the same time and let faster downloads continue without waiting for the one that’s delayed.

You apply the Task.WhenAll method to a collection of tasks. The application of WhenAll returns a single task that isn’t complete until every task in the collection is completed. The tasks appear to run in parallel, but no additional threads are created. The tasks can complete in any order.

Important note Important

The following procedures describe extensions to the async applications that are developed in Walkthrough: Accessing the Web by Using Async and Await (C# and Visual Basic). You can develop the applications by either completing the walkthrough or downloading the code from Developer Code Samples.

To run the example, you must have Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 for Windows Desktop, Visual Studio Express 2013 for Windows, or the .NET Framework 4.5 or 4.5.1 installed on your computer.

To add Task.WhenAll to your GetURLContentsAsync solution

  1. Add the ProcessURLAsync method to the first application that's developed in Walkthrough: Accessing the Web by Using Async and Await (C# and Visual Basic).

    • If you downloaded the code from Developer Code Samples, open the AsyncWalkthrough project, and then add ProcessURLAsync to the MainWindow.xaml.vb or MainWindow.xaml.cs file.

    • If you developed the code by completing the walkthrough, add ProcessURLAsync to the application that includes the GetURLContentsAsync method. The MainWindow.xaml.vb or MainWindow.xaml.cs file for this application is the first example in the "Complete Code Examples from the Walkthrough" section.

    The ProcessURLAsync method consolidates the actions in the body of the For Each or foreach loop in SumPageSizesAsync in the original walkthrough. The method asynchronously downloads the contents of a specified website as a byte array, and then displays and returns the length of the byte array.

    private async Task<int> ProcessURLAsync(string url)
    {
        var byteArray = await GetURLContentsAsync(url);
        DisplayResults(url, byteArray);
        return byteArray.Length;
    }
    
  2. Comment out or delete the For Each or foreach loop in SumPageSizesAsync, as the following code shows.

    //var total = 0; 
    //foreach (var url in urlList) 
    //{ 
    //    byte[] urlContents = await GetURLContentsAsync(url); 
    
    //    // The previous line abbreviates the following two assignment statements. 
    //    // GetURLContentsAsync returns a Task<T>. At completion, the task 
    //    // produces a byte array. 
    //    //Task<byte[]> getContentsTask = GetURLContentsAsync(url); 
    //    //byte[] urlContents = await getContentsTask; 
    
    //    DisplayResults(url, urlContents); 
    
    //    // Update the total.           
    //    total += urlContents.Length; 
    //}
    
  3. Create a collection of tasks. The following code defines a query that, when executed by the ToArray<TSource> method, creates a collection of tasks that download the contents of each website. The tasks are started when the query is evaluated.

    Add the following code to method SumPageSizesAsync after the declaration of urlList.

    // Create a query. 
    IEnumerable<Task<int>> downloadTasksQuery = 
        from url in urlList select ProcessURLAsync(url);
    
    // Use ToArray to execute the query and start the download tasks.
    Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
    
  4. Apply Task.WhenAll to the collection of tasks, downloadTasks. Task.WhenAll returns a single task that finishes when all the tasks in the collection of tasks have completed.

    In the following example, the Await or await expression awaits the completion of the single task that WhenAll returns. The expression evaluates to an array of integers, where each integer is the length of a downloaded website. Add the following code to SumPageSizesAsync, just after the code that you added in the previous step.

    // Await the completion of all the running tasks. 
    int[] lengths = await Task.WhenAll(downloadTasks);
    
    //// The previous line is equivalent to the following two statements. 
    //Task<int[]> whenAllTask = Task.WhenAll(downloadTasks); 
    //int[] lengths = await whenAllTask;
    
  5. Finally, use the Sum method to calculate the sum of the lengths of all the websites. Add the following line to SumPageSizesAsync.

    int total = lengths.Sum();
    

To add Task.WhenAll to the HttpClient.GetByteArrayAsync solution

  1. Add the following version of ProcessURLAsync to the second application that's developed in Walkthrough: Accessing the Web by Using Async and Await (C# and Visual Basic).

    • If you downloaded the code from Developer Code Samples, open the AsyncWalkthrough_HttpClient project, and then add ProcessURLAsync to the MainWindow.xaml.vb or MainWindow.xaml.cs file.

    • If you developed the code by completing the walkthrough, add ProcessURLAsync to the application that uses the HttpClient.GetByteArrayAsync method. The MainWindow.xaml.vb or MainWindow.xaml.cs file for this application is the second example in the "Complete Code Examples from the Walkthrough" section.

    The ProcessURLAsync method consolidates the actions in the body of the For Each or foreach loop in SumPageSizesAsync in the original walkthrough. The method asynchronously downloads the contents of a specified website as a byte array, and then displays and returns the length of the byte array.

    The only difference from the ProcessURLAsync method in the previous procedure is the use of the HttpClient instance, client.

    async Task<int> ProcessURL(string url, HttpClient client)
    {
        byte[] byteArray = await client.GetByteArrayAsync(url);
        DisplayResults(url, byteArray);
        return byteArray.Length;
    }
    
  2. Comment out or delete the For Each or foreach loop in SumPageSizesAsync, as the following code shows.

    //var total = 0; 
    //foreach (var url in urlList) 
    //{ 
    //    // GetByteArrayAsync returns a Task<T>. At completion, the task 
    //    // produces a byte array. 
    //    byte[] urlContent = await client.GetByteArrayAsync(url); 
    
    //    // The previous line abbreviates the following two assignment 
    //    // statements. 
    //    Task<byte[]> getContentTask = client.GetByteArrayAsync(url); 
    //    byte[] urlContent = await getContentTask; 
    
    //    DisplayResults(url, urlContent); 
    
    //    // Update the total. 
    //    total += urlContent.Length; 
    //}
    
  3. Define a query that, when executed by the ToArray<TSource> method, creates a collection of tasks that download the contents of each website. The tasks are started when the query is evaluated.

    Add the following code to method SumPageSizesAsync after the declaration of client and urlList.

    // Create a query.
    IEnumerable<Task<int>> downloadTasksQuery = 
        from url in urlList select ProcessURL(url, client);
    
    // Use ToArray to execute the query and start the download tasks.
    Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
    
  4. Next, apply Task.WhenAll to the collection of tasks, downloadTasks. Task.WhenAll returns a single task that finishes when all the tasks in the collection of tasks have completed.

    In the following example, the Await or await expression awaits the completion of the single task that WhenAll returns. When complete, the Await or await expression evaluates to an array of integers, where each integer is the length of a downloaded website. Add the following code to SumPageSizesAsync, just after the code that you added in the previous step.

    // Await the completion of all the running tasks. 
    int[] lengths = await Task.WhenAll(downloadTasks);
    
    //// The previous line is equivalent to the following two statements. 
    //Task<int[]> whenAllTask = Task.WhenAll(downloadTasks); 
    //int[] lengths = await whenAllTask;
    
  5. Finally, use the Sum method to get the sum of the lengths of all the websites. Add the following line to SumPageSizesAsync.

    int total = lengths.Sum();
    

To test the Task.WhenAll solutions

The following code shows the extensions to the project that uses the GetURLContentsAsync method to download content from the web.

// Add the following using directives, and add a reference for System.Net.Http. 
using System.Net.Http;
using System.IO;
using System.Net;

namespace AsyncExampleWPF_WhenAll
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }


        private async void startButton_Click(object sender, RoutedEventArgs e)
        {
            resultsTextBox.Clear();

            // Two-step async call.
            Task sumTask = SumPageSizesAsync();
            await sumTask;

            // One-step async call. 
            //await SumPageSizesAsync();

            resultsTextBox.Text += "\r\nControl returned to startButton_Click.\r\n";
        }


        private async Task SumPageSizesAsync()
        {
            // Make a list of web addresses.
            List<string> urlList = SetUpURLList();

            // Create a query. 
            IEnumerable<Task<int>> downloadTasksQuery = 
                from url in urlList select ProcessURLAsync(url);

            // Use ToArray to execute the query and start the download tasks.
            Task<int>[] downloadTasks = downloadTasksQuery.ToArray();

            // You can do other work here before awaiting. 

            // Await the completion of all the running tasks. 
            int[] lengths = await Task.WhenAll(downloadTasks);

            //// The previous line is equivalent to the following two statements. 
            //Task<int[]> whenAllTask = Task.WhenAll(downloadTasks); 
            //int[] lengths = await whenAllTask; 

            int total = lengths.Sum();

            //var total = 0; 
            //foreach (var url in urlList) 
            //{ 
            //    byte[] urlContents = await GetURLContentsAsync(url); 

            //    // The previous line abbreviates the following two assignment statements. 
            //    // GetURLContentsAsync returns a Task<T>. At completion, the task 
            //    // produces a byte array. 
            //    //Task<byte[]> getContentsTask = GetURLContentsAsync(url); 
            //    //byte[] urlContents = await getContentsTask; 

            //    DisplayResults(url, urlContents); 

            //    // Update the total.           
            //    total += urlContents.Length; 
            //} 

            // Display the total count for all of the websites.
            resultsTextBox.Text +=
                string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);
        }


        private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "http://msdn.microsoft.com",
                "http://msdn.microsoft.com/library/windows/apps/br211380.aspx",
                "http://msdn.microsoft.com/en-us/library/hh290136.aspx",
                "http://msdn.microsoft.com/en-us/library/ee256749.aspx",
                "http://msdn.microsoft.com/en-us/library/hh290138.aspx",
                "http://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"
            };
            return urls;
        }



        // The actions from the foreach loop are moved to this async method. 
        private async Task<int> ProcessURLAsync(string url)
        {
            var byteArray = await GetURLContentsAsync(url);
            DisplayResults(url, byteArray);
            return byteArray.Length;
        }


        private async Task<byte[]> GetURLContentsAsync(string url)
        {
            // The downloaded resource ends up in the variable named content. 
            var content = new MemoryStream();

            // Initialize an HttpWebRequest for the current URL. 
            var webReq = (HttpWebRequest)WebRequest.Create(url);

            // Send the request to the Internet resource and wait for 
            // the response. 
            using (WebResponse response = await webReq.GetResponseAsync())
            {
                // Get the data stream that is associated with the specified url. 
                using (Stream responseStream = response.GetResponseStream())
                {
                    await responseStream.CopyToAsync(content);
                }
            }

            // Return the result as a byte array. 
            return content.ToArray();

        }


        private void DisplayResults(string url, byte[] content)
        {
            // Display the length of each website. The string format  
            // is designed to be used with a monospaced font, such as 
            // Lucida Console or Global Monospace. 
            var bytes = content.Length;
            // Strip off the "http://".
            var displayURL = url.Replace("http://", "");
            resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);

        }
    }
}

The following code shows the extensions to the project that uses method HttpClient.GetByteArrayAsync to download content from the web.

// Add the following using directives, and add a reference for System.Net.Http. 
using System.Net.Http;
using System.IO;
using System.Net;

namespace AsyncExampleWPF_HttpClient_WhenAll
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void startButton_Click(object sender, RoutedEventArgs e)
        {
            resultsTextBox.Clear();

            // One-step async call.
            await SumPageSizesAsync();

            // Two-step async call. 
            //Task sumTask = SumPageSizesAsync(); 
            //await sumTask;

            resultsTextBox.Text += "\r\nControl returned to startButton_Click.\r\n";
        }


        private async Task SumPageSizesAsync()
        {
            // Make a list of web addresses.
            List<string> urlList = SetUpURLList();


            // Declare an HttpClient object and increase the buffer size. The 
            // default buffer size is 65,536.
            HttpClient client = new HttpClient() { MaxResponseContentBufferSize = 1000000 };

            // Create a query.
            IEnumerable<Task<int>> downloadTasksQuery = 
                from url in urlList select ProcessURL(url, client);

            // Use ToArray to execute the query and start the download tasks.
            Task<int>[] downloadTasks = downloadTasksQuery.ToArray();

            // You can do other work here before awaiting. 

            // Await the completion of all the running tasks. 
            int[] lengths = await Task.WhenAll(downloadTasks);

            //// The previous line is equivalent to the following two statements. 
            //Task<int[]> whenAllTask = Task.WhenAll(downloadTasks); 
            //int[] lengths = await whenAllTask; 

            int total = lengths.Sum();

            //var total = 0; 
            //foreach (var url in urlList) 
            //{ 
            //    // GetByteArrayAsync returns a Task<T>. At completion, the task 
            //    // produces a byte array. 
            //    byte[] urlContent = await client.GetByteArrayAsync(url); 

            //    // The previous line abbreviates the following two assignment 
            //    // statements. 
            //    Task<byte[]> getContentTask = client.GetByteArrayAsync(url); 
            //    byte[] urlContent = await getContentTask; 

            //    DisplayResults(url, urlContent); 

            //    // Update the total. 
            //    total += urlContent.Length; 
            //} 

            // Display the total count for all of the web addresses.
            resultsTextBox.Text +=
                string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);
        }


        private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "http://msdn.microsoft.com",
                "http://msdn.microsoft.com/en-us/library/hh290136.aspx",
                "http://msdn.microsoft.com/en-us/library/ee256749.aspx",
                "http://msdn.microsoft.com/en-us/library/hh290138.aspx",
                "http://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"
            };
            return urls;
        }


        // The actions from the foreach loop are moved to this async method.
        async Task<int> ProcessURL(string url, HttpClient client)
        {
            byte[] byteArray = await client.GetByteArrayAsync(url);
            DisplayResults(url, byteArray);
            return byteArray.Length;
        }


        private void DisplayResults(string url, byte[] content)
        {
            // Display the length of each web site. The string format  
            // is designed to be used with a monospaced font, such as 
            // Lucida Console or Global Monospace. 
            var bytes = content.Length;
            // Strip off the "http://".
            var displayURL = url.Replace("http://", "");
            resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);
        }
    }
}
Show:
© 2014 Microsoft