Cancel Remaining Async Tasks after One Is Complete (C# and Visual Basic)

By using the Task.WhenAny method together with a CancellationToken, you can cancel all remaining tasks when one task is complete. The WhenAny method takes an argument that’s a collection of tasks. The method starts all the tasks and returns a single task. The single task is complete when any task in the collection is complete.

This example demonstrates how to use a cancellation token in conjunction with WhenAny to hold onto the first task to finish from the collection of tasks and to cancel the remaining tasks. Each task downloads the contents of a website. The example displays the length of the contents of the first download to complete and cancels the other downloads.

Note Note

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

You can download the complete Windows Presentation Foundation (WPF) project from Async Sample: Fine Tuning Your Application and then follow these steps.

  1. Decompress the file that you downloaded, and then start Visual Studio 2012.

  2. On the menu bar, choose File, Open, Project/Solution.

  3. In the Open Project dialog box, open the folder that holds the sample code that you decompressed, and then open the solution (.sln) file for AsyncFineTuningCS or AsyncFineTuningVB.

  4. In Solution Explorer, open the shortcut menu for the CancelAfterOneTask project, and then choose Set as StartUp Project.

  5. Choose the F5 key to run the project.

    Choose the Ctrl+F5 keys to run the project without debugging it.

  6. Run the program several times to verify that different downloads finish first.

If you don't want to download the project, you can review the MainWindow.xaml.vb and MainWindow.xaml.cs files at the end of this topic.

The example in this topic adds to the project that's developed in Cancel an Async Task or a List of Tasks (C# and Visual Basic) to cancel a list of tasks. The example uses the same UI, although the Cancel button isn’t used explicitly.

To build the example yourself, step by step, follow the instructions in the "Downloading the Example" section, but choose CancelAListOfTasks as the StartUp Project. Add the changes in this topic to that project.

In the MainWindow.xaml.vb or MainWindow.xaml.cs file of the CancelAListOfTasks project, start the transition by moving the processing steps for each website from the loop in AccessTheWebAsync to the following async method.

// ***Bundle the processing steps for a website into one async method.
async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)
{
    // GetAsync returns a Task<HttpResponseMessage>. 
    HttpResponseMessage response = await client.GetAsync(url, ct);

    // Retrieve the website contents from the HttpResponseMessage. 
    byte[] urlContents = await response.Content.ReadAsByteArrayAsync();

    return urlContents.Length;
}

In AccessTheWebAsync, this example uses a query, the ToArray<TSource> method, and the WhenAny method to create and start an array of tasks. The application of WhenAny to the array returns a single task that, when awaited, evaluates to the first task to reach completion in the array of tasks.

Make the following changes in AccessTheWebAsync. Asterisks mark the changes in the code file.

  1. Comment out or delete the loop.

  2. Create a query that, when executed, produces a collection of generic tasks. Each call to ProcessURLAsync returns a Task<TResult> where TResult is an integer.

    // ***Create a query that, when executed, returns a collection of tasks.
    IEnumerable<Task<int>> downloadTasksQuery =
        from url in urlList select ProcessURLAsync(url, client, ct);
    
  3. Call ToArray to execute the query and start the tasks. The application of the WhenAny method in the next step would execute the query and start the tasks without using ToArray, but other methods might not. The safest practice is to force execution of the query explicitly.

    // ***Use ToArray to execute the query and start the download tasks. 
    Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
    
  4. Call WhenAny on the collection of tasks. WhenAny returns a Task(Of Task(Of Integer)) or Task<Task<int>>. That is, WhenAny returns a task that evaluates to a single Task(Of Integer) or Task<int> when it’s awaited. That single task is the first task in the collection to finish. The task that finished first is assigned to firstFinishedTask. The type of firstFinishedTask is Task<TResult> where TResult is an integer because that's the return type of ProcessURLAsync.

    // ***Call WhenAny and then await the result. The task that finishes  
    // first is assigned to firstFinishedTask.
    Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
    
  5. In this example, you’re interested only in the task that finishes first. Therefore, use CancellationTokenSource.Cancel to cancel the remaining tasks.

    // ***Cancel the rest of the downloads. You just want the first one.
    cts.Cancel();
    
  6. Finally, await firstFinishedTask to retrieve the length of the downloaded content.

    var length = await firstFinishedTask;
    resultsTextBox.Text += String.Format("\r\nLength of the downloaded website:  {0}\r\n", length);
    

Run the program several times to verify that different downloads finish first.

The following code is the complete MainWindow.xaml.vb or MainWindow.xaml.cs file for the example. Asterisks mark the elements that were added for this example.

Notice that you must add a reference for System.Net.Http.

You can download the project from Async Sample: Fine Tuning Your Application.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

// Add a using directive and a reference for System.Net.Http. 
using System.Net.Http;

// Add the following using directive. 
using System.Threading;

namespace CancelAfterOneTask
{
    public partial class MainWindow : Window
    {
        // Declare a System.Threading.CancellationTokenSource.
        CancellationTokenSource cts;

        public MainWindow()
        {
            InitializeComponent();
        }


        private async void startButton_Click(object sender, RoutedEventArgs e)
        {
            // Instantiate the CancellationTokenSource.
            cts = new CancellationTokenSource();

            resultsTextBox.Clear();

            try
            {
                await AccessTheWebAsync(cts.Token);
                resultsTextBox.Text += "\r\nDownload complete.";
            }
            catch (OperationCanceledException)
            {
                resultsTextBox.Text += "\r\nDownload canceled.";
            }
            catch (Exception)
            {
                resultsTextBox.Text += "\r\nDownload failed.";
            }

            // Set the CancellationTokenSource to null when the download is complete.
            cts = null;
        }


        // You can still include a Cancel button if you want to. 
        private void cancelButton_Click(object sender, RoutedEventArgs e)
        {
            if (cts != null)
            {
                cts.Cancel();
            }
        }


        // Provide a parameter for the CancellationToken.
        async Task AccessTheWebAsync(CancellationToken ct)
        {
            HttpClient client = new HttpClient();

            // Call SetUpURLList to make a list of web addresses.
            List<string> urlList = SetUpURLList();

            // ***Comment out or delete the loop. 
            //foreach (var url in urlList) 
            //{ 
            //    // GetAsync returns a Task<HttpResponseMessage>.  
            //    // Argument ct carries the message if the Cancel button is chosen.  
            //    // ***Note that the Cancel button can cancel all remaining downloads. 
            //    HttpResponseMessage response = await client.GetAsync(url, ct); 

            //    // Retrieve the website contents from the HttpResponseMessage. 
            //    byte[] urlContents = await response.Content.ReadAsByteArrayAsync(); 

            //    resultsTextBox.Text += 
            //        String.Format("\r\nLength of the downloaded string: {0}.\r\n", urlContents.Length);
            //} 

            // ***Create a query that, when executed, returns a collection of tasks.
            IEnumerable<Task<int>> downloadTasksQuery =
                from url in urlList select ProcessURLAsync(url, client, ct);

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

            // ***Call WhenAny and then await the result. The task that finishes  
            // first is assigned to firstFinishedTask.
            Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);

            // ***Cancel the rest of the downloads. You just want the first one.
            cts.Cancel();

            // ***Await the first completed task and display the results.  
            // Run the program several times to demonstrate that different 
            // websites can finish first. 
            var length = await firstFinishedTask;
            resultsTextBox.Text += String.Format("\r\nLength of the downloaded website:  {0}\r\n", length);
        }


        // ***Bundle the processing steps for a website into one async method.
        async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)
        {
            // GetAsync returns a Task<HttpResponseMessage>. 
            HttpResponseMessage response = await client.GetAsync(url, ct);

            // Retrieve the website contents from the HttpResponseMessage. 
            byte[] urlContents = await response.Content.ReadAsByteArrayAsync();

            return urlContents.Length;
        }


        // Add a method that creates a list of web addresses. 
        private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "http://msdn.microsoft.com",
                "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;
        }
    }
    // Sample output: 

    // Length of the downloaded website:  158856 

    // Download complete.
}
Was this page helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft