方法: Task.WhenAll を使用して AsyncWalkthrough を拡張する (C#)How to: Extend the async Walkthrough by Using Task.WhenAll (C#)

チュートリアル: Async と Await を使用した Web へのアクセス (C#)」の非同期ソリューションのパフォーマンスを Task.WhenAll メソッドを使用して向上させることができます。You can improve the performance of the async solution in Walkthrough: Accessing the Web by Using async and await (C#) by using the Task.WhenAll method. このメソッドは、タスクのコレクションとして表される、複数の非同期操作を非同期に待機します。This method asynchronously awaits multiple asynchronous operations, which are represented as a collection of tasks.

このチュートリアルでは、Web サイトが異なる速度でダウンロードされることに気付きます。You might have noticed in the walkthrough that the websites download at different rates. Web サイトの 1 つが非常に低速な場合があります。これは残りのすべてのダウンロードを遅延させます。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.

タスクのコレクションに Task.WhenAll メソッドを適用します。You apply the Task.WhenAll method to a collection of tasks. WhenAll を適用すると、コレクション内のすべてのタスクが完了するまで完了しない 1 つのタスクを返します。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.

重要

次の手順では、「チュートリアル: Async と Await を使用した Web へのアクセス (C#)」で開発した非同期アプリケーションの拡張機能について説明します。The following procedures describe extensions to the async applications that are developed in Walkthrough: Accessing the Web by Using async and await (C#). チュートリアルを完了するか、開発者コード サンプルからコードをダウンロードして、アプリケーションを開発できます。You can develop the applications by either completing the walkthrough or downloading the code from Developer Code Samples.

例を実行するには、Visual Studio 2012 以降がコンピューターにインストールされている必要があります。To run the example, you must have Visual Studio 2012 or later installed on your computer.

GetURLContentsAsync ソリューションに Task.WhenAll を追加するにはTo add Task.WhenAll to your GetURLContentsAsync solution

  1. ProcessURLAsync メソッドを「チュートリアル: Async と Await を使用した Web へのアクセス (C#)」で開発した最初のアプリケーションに追加します。Add the ProcessURLAsync method to the first application that's developed in Walkthrough: Accessing the Web by Using async and await (C#).

    • コードを開発者コード サンプルからダウンロードした場合は、AsyncWalkthrough プロジェクトを開き、ProcessURLAsync を MainWindow.xaml.cs ファイルに追加します。If you downloaded the code from Developer Code Samples, open the AsyncWalkthrough project, and then add ProcessURLAsync to the MainWindow.xaml.cs file.

    • チュートリアルを実行してコードを開発する場合は、ProcessURLAsync メソッドを含むアプリケーションに GetURLContentsAsync を追加します。If you developed the code by completing the walkthrough, add ProcessURLAsync to the application that includes the GetURLContentsAsync method. このアプリケーションの MainWindow.xaml.cs ファイルは、「チュートリアルの完全なコード例」のセクションにある 1 つ目の例です。The MainWindow.xaml.cs file for this application is the first example in the "Complete Code Examples from the Walkthrough" section.

    ProcessURLAsync メソッドは、元のチュートリアルの SumPageSizesAsyncforeach ループの本体にあるアクションを統合します。The ProcessURLAsync method consolidates the actions in the body of the foreach loop in SumPageSizesAsync in the original walkthrough. このメソッドは、指定した Web サイトのコンテンツをバイト配列として非同期的にダウンロードし、バイト配列の長さを表示して返します。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. 次のコードに示すように、SumPageSizesAsyncforeach ループをコメント アウトするか削除します。Comment out or delete the 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. 次のコードは、ToArray メソッドによって実行されるときに、各 Web サイトのコンテンツをダウンロードするタスクのコレクションを作成するクエリを定義します。The following code defines a query that, when executed by the ToArray method, creates a collection of tasks that download the contents of each website. タスクは、クエリが評価されるときに開始されます。The tasks are started when the query is evaluated.

    SumPageSizesAsync の宣言の後の urlList メソッドに、次のコードを追加します。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. Task.WhenAll をタスクのコレクション downloadTasks に適用します。Apply Task.WhenAll to the collection of tasks, downloadTasks. Task.WhenAll は、タスクのコレクションのすべてのタスクが完了すると完了する、単一のタスクを返します。Task.WhenAll returns a single task that finishes when all the tasks in the collection of tasks have completed.

    次の例では、await 式は、WhenAll によって返される単一のタスクが完了するのを待機します。In the following example, the 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. SumPageSizesAsync の、前の手順で追加したコードの直後に、次のコードを追加します。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. 最後に Sum メソッドを使って、すべての Web サイトの長さの合計を計算します。Finally, use the Sum method to calculate the sum of the lengths of all the websites. SumPageSizesAsync に次の行を追加します。Add the following line to SumPageSizesAsync.

    int total = lengths.Sum();
    

HttpClient.GetByteArrayAsync ソリューションに Task.WhenAll を追加するにはTo add Task.WhenAll to the HttpClient.GetByteArrayAsync solution

  1. ProcessURLAsync の次のバージョンを「チュートリアル: Async と Await を使用した Web へのアクセス (C#)」で開発した 2 番目のアプリケーションに追加します。Add the following version of ProcessURLAsync to the second application that's developed in Walkthrough: Accessing the Web by Using async and await (C#).

    • コードを開発者コード サンプルからダウンロードした場合は、AsyncWalkthrough_HttpClient プロジェクトを開き、ProcessURLAsync を MainWindow.xaml.cs ファイルに追加します。If you downloaded the code from Developer Code Samples, open the AsyncWalkthrough_HttpClient project, and then add ProcessURLAsync to the MainWindow.xaml.cs file.

    • チュートリアルを実行してコードを開発する場合は、ProcessURLAsync メソッドを使うアプリケーションに HttpClient.GetByteArrayAsync を追加します。If you developed the code by completing the walkthrough, add ProcessURLAsync to the application that uses the HttpClient.GetByteArrayAsync method. このアプリケーションの MainWindow.xaml.cs ファイルは、「チュートリアルの完全なコード例」のセクションにある 2 つ目の例です。The MainWindow.xaml.cs file for this application is the second example in the "Complete Code Examples from the Walkthrough" section.

    ProcessURLAsync メソッドは、元のチュートリアルの SumPageSizesAsyncforeach ループの本体にあるアクションを統合します。The ProcessURLAsync method consolidates the actions in the body of the foreach loop in SumPageSizesAsync in the original walkthrough. このメソッドは、指定した Web サイトのコンテンツをバイト配列として非同期的にダウンロードし、バイト配列の長さを表示して返します。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.

    前の手順の ProcessURLAsync メソッドとの唯一の違いは、HttpClient インスタンス client の使用です。The only difference from the ProcessURLAsync method in the previous procedure is the use of the HttpClient instance, client.

    async Task<int> ProcessURLAsync(string url, HttpClient client)
    {
        byte[] byteArray = await client.GetByteArrayAsync(url);
        DisplayResults(url, byteArray);
        return byteArray.Length;
    }
    
  2. 次のコードに示すように、For Eachforeach または SumPageSizesAsync のループをコメント アウトするか削除します。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. ToArray メソッドによって実行されるときに、各 Web サイトのコンテンツをダウンロードするタスクのコレクションを作成するクエリを定義します。Define a query that, when executed by the ToArray method, creates a collection of tasks that download the contents of each website. タスクは、クエリが評価されるときに開始されます。The tasks are started when the query is evaluated.

    SumPageSizesAsync および client の宣言の後の urlList メソッドに、次のコードを追加します。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 ProcessURLAsync(url, client);
    
    // Use ToArray to execute the query and start the download tasks.
    Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
    
  4. 次に、Task.WhenAll をタスクのコレクション downloadTasks に適用します。Next, apply Task.WhenAll to the collection of tasks, downloadTasks. Task.WhenAll は、タスクのコレクションのすべてのタスクが完了すると完了する、単一のタスクを返します。Task.WhenAll returns a single task that finishes when all the tasks in the collection of tasks have completed.

    次の例では、await 式は、WhenAll によって返される単一のタスクが完了するのを待機します。In the following example, the await expression awaits the completion of the single task that WhenAll returns. 完了すると、await 式は、各整数がダウンロードされたサイトの長さである、整数の配列に評価します。When complete, the await expression evaluates to an array of integers, where each integer is the length of a downloaded website. SumPageSizesAsync の、前の手順で追加したコードの直後に、次のコードを追加します。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. 最後に Sum メソッドを使って、すべての Web サイトの長さの合計を計算します。Finally, use the Sum method to get the sum of the lengths of all the websites. SumPageSizesAsync に次の行を追加します。Add the following line to SumPageSizesAsync.

    int total = lengths.Sum();
    

Task.WhenAll ソリューションをテストするにはTo test the Task.WhenAll solutions

Example

次のコードは、GetURLContentsAsync メソッドを使用して Web からコンテンツをダウンロードするプロジェクトの拡張機能を示します。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 +=
                $"\r\n\r\nTotal bytes returned:  {total}\r\n";
        }

        private List<string> SetUpURLList()
        {
            List<string> urls = new List<string>
            {
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
                "https://msdn.microsoft.com/library/hh290136.aspx",
                "https://msdn.microsoft.com/library/ee256749.aspx",
                "https://msdn.microsoft.com/library/hh290138.aspx",
                "https://msdn.microsoft.com/library/hh290140.aspx",
                "https://msdn.microsoft.com/library/dd470362.aspx",
                "https://msdn.microsoft.com/library/aa578028.aspx",
                "https://msdn.microsoft.com/library/ms404677.aspx",
                "https://msdn.microsoft.com/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 "https://".
            var displayURL = url.Replace("https://", "");
            resultsTextBox.Text += $"\n{displayURL,-58} {bytes,8}";
        }
    }
}

Example

次のコードは、HttpClient.GetByteArrayAsync メソッドを使用して Web からコンテンツをダウンロードするプロジェクトの拡張機能を示します。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 ProcessURLAsync(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 +=
                $"\r\n\r\nTotal bytes returned:  {total}\r\n";
        }

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

        // The actions from the foreach loop are moved to this async method.
        async Task<int> ProcessURLAsync(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 "https://".
            var displayURL = url.Replace("https://", "");
            resultsTextBox.Text += $"\n{displayURL,-58} {bytes,8}";
        }
    }
}

関連項目See also