Search code examples
c#asynchronousmultitaskingcancellationtokensourcecancellation-token

C# Cancel Task with started List<>


I started task with lists and await Task.WhenAll

private async void btn_download_Click(object sender, EventArgs e)
    {
        .
        .
        .
        await DownloadMultipleFilesAsync(old_json);
        Console.WriteLine("Download completed.");
    }

and that's my code that starting task with list.

private async Task DownloadMultipleFilesAsync(List<media> doclist)
    {
        var token = cancelTokenSource.Token;
        await Task.WhenAll(doclist.Select(doc => DownloadFileAsync(doc)));
        btn_download.Enabled = true;
    }

and my download method

private async Task DownloadFileAsync(media media)
{
    .
    .
    .
    Console.WriteLine(media.no + media_ext + " started.");
    webClient.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
    await webClient.DownloadFileTaskAsync(new Uri(media.url), @downloadToDirectory);
    Console.WriteLine(media.no + media_ext + " finished.");
    .
    .
    .
}

The output window like this:

1.jpg started.
2.jpg started.
3.jpg started.
4.jpg started.
5.jpg started.
6.jpg started.
7.jpg started.
8.jpg started.
9.jpg started.
10.jpg started.
11.jpg started.
12.jpg started.
13.jpg started.
14.jpg started.
15.jpg started.
16.jpg started.
17.jpg started.
18.jpg started.
19.jpg started.
1.jpg finished.
4.jpg finished.
2.jpg finished.
6.jpg finished.
8.jpg finished.
10.jpg finished.
3.jpg finished.
5.jpg finished.
12.jpg finished.
14.jpg finished.
7.jpg finished.
16.jpg finished.
18.jpg finished.
9.jpg finished.
11.jpg finished.
13.jpg finished.
15.jpg finished.
17.jpg finished.
19.jpg finished.
Download completed.

I want to click btn_cancel and cancel starting task and wait finish started task.

private void btn_cancel_Click(object sender, EventArgs e)
{
    cancelTokenSource.Cancel();
    cancelTokenSource = new CancellationTokenSource();
}

Solution

  • What you need to do is pass the cancellation token down the call chain then use it where possible, you also need to register a cancel call back to call WebClient.CancelAsync() to cancel the download.

    private async void btn_download_Click(object sender, EventArgs e)
    {
        .
        .
        .
        var token = cancelTokenSource.Token;
        try
        {
            await DownloadMultipleFilesAsync(old_json, token);
            Console.WriteLine("Download completed.");
        }
        catch(OperationCanceledException ex)
        {
            //If something other than our token caused the cancel bubble up the exception.
            if(ex.CancellationToken != token)
                throw;
        }
    }
    
    
    private async Task DownloadMultipleFilesAsync(List<media> doclist, CancellationToken token)
    {
        await Task.WhenAll(doclist.Select(doc => DownloadFileAsync(doc, token));
        btn_download.Enabled = true;
    }
    
    
    private async Task DownloadFileAsync(media media, CancellationToken token)
    {
        .
        .
        .
        Console.WriteLine(media.no + media_ext + " started.");
        webClient.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
        try
        {
            using(token.Register(() => webClient.CancelAsync()))
            {
                await webClient.DownloadFileTaskAsync(new Uri(media.url), @downloadToDirectory);
            }
        }            
        catch (WebException ex)
        {
            //Raise a OperationCanceledException if the request was canceled, otherwise bubble up the exception.
            if(ex.Status == WebExceptionStatus.RequestCanceled)
                throw new OperationCanceledException(token);
            else
                throw;
        }
        Console.WriteLine(media.no + media_ext + " finished.");
        .
        .
        .
    }