Click here to Skip to main content
15,867,686 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more: , +
Hello Experts,

I am working on an asp.net core application which has downloading files functionality. Using this function we can create multiple download tasks and execute them at once using Taks.WhenAll<list<downloadtasks>>.

I am trying to implement cancel download functionality using which we can abort/cancel the complete download operation by clicking a cancel button on one of my razor pages (if I selected 100 files to download and clicked cancel after downloading 10 files, 90 remaining files should not be downloaded).

Currently it cannot be cancelled, once the download operation is started it continuously executes in background even if we close the download page unless all files fail/succeed to download. Exiting the application stops it.

Implementation is as shown below.

I've spent much time on internet, gone through a lot of different articles over cancellation of tasks, tried to implement multiple approaches given in these articles, but unable to cancel the operation.
Any help would be appreciated.
Thanks in advance.

What I have tried:

<pre>**DownloadsController class :-**

//variable declaration
CancellationTokenSource cts;
    
[HttpPost]
public async Task<IActionResult> Download_iles(DownloadFilesModel downloadFilesModel)
{
     cts=new CancellationTokenSource(); 
     var successFiles = await _downloadManager.DownloadAsyncpa(downloadFilesModel.param1, downloadFilesModel.param2, cts.Token);
 }

[HttpPost]
public IActionResult StopDownloadOperation()
{  
     cts?.Cancel();
     return Ok();
}

**ManageFileDownlods class :-**
 public class ManageFileDownlods : BackgroundService, IManageFileDownlods
 {
     //Constructor
      public ManageFileDownlods(IDownloadmaster downloadmaster)
        {
            _downloadmaster = downloadmaster;
        }
        
    public async Task<List<DownloadFile>>      
  DownloadAsync (funcparam1,funcparam2,CancellationToken cancellationToken=default)
    {
      // For each server in serverQueue these multiple tasks will execure
        while (serverQueue.Count > 0)
        {   
            //Multiple tasks created to downloadfiles
            downloadTasksList.Add(ServerProcess(funcparam1, funcparam2, cancellationToken));
            //Multiple tasks executed here to download files
            try
            {
            await Task.WhenAll(downloadTasksList.ToArray());  
            }
            catch()
            { }             
        }            
    }
    
private async Task<List<DownloadFile>> ServerProcess (funcparam1, funcparam2,         
CancellationToken cancellationToken)
    {
             while (funcparam1.Count > 0)
            {
                //5 taks created in loop
              for (int i = 0; i < 5; i++)
              {
                    //Multiple tasks created to downloadfiles
               fileDownlodTasks.Add(_downloadmaster.Download(param1, param2,    
              cancellationToken));
                    await Task.Delay(300);
                }

                try
                {
                    //Multiple tasks executed here to download files
                    await Task.WhenAll(fileDownlodTasks.ToArray());                     
                }
                catch (TaskCanceledException ex)
                {
                    Debug.WriteLine("execution stopped");
                    throw ex;
                }
            }
      }
 }
    
    
    **Downloadmaster Class:-**
     public async Task<DownloadFile> Download (param1,param2,CancellationToken cancellationToken)
    {
        //actual function which initiated file download from server
        var filecontents = DownloadFileFromServer (param1,param2, cancellationToken);
    }
Posted
Updated 14-Dec-22 1:40am
Comments
George Swan 15-Dec-22 2:56am    
Cancellation is not automatic. Your code needs to continually monitor the status of the CancellationToken and to end the downloading when cancellation is requested. I would suggest that you read Stephen Cleary's excellent 4 part series on task cancellation
https://blog.stephencleary.com/2022/02/cancellation-1-overview.html

1 solution

Each request to your controller will create a new instance of the class to process that request. The request to the StopDownloadOperation action will not have access to the CancellationTokenSource created by the DownloadFiles action.

If you're triggering the download via a Javascript call, you may be able to use an AbortController instead[^]:
C#
[HttpPost]
public async Task<IActionResult> DownloadFiles(DownloadFilesModel downloadFilesModel, CancellationToken cancellationToken)
{
     var successFiles = await _downloadManager.DownloadAsync(downloadFilesModel.param1, downloadFilesModel.param2, cancellationToken);
     return Ok(successFiles);
}
JavaScript
const startButton = document.getElementById("startDownloadButton");
const stopButton = document.getElementById("stopDownloadButton");

let abortController = null;

startButton.addEventListener("click", async (e) => {
    e.preventDefault();
    
    abortController?.abort();
    abortController = new AbortController();
    
    stopButton.disabled = false;
    startButton.disabled = true;
    try {
        
        const response = await fetch(url, {
            method: "POST",
            body: { ... },
            signal: abortController.signal
        });
        
        if (!response.ok) {
            console.error(response.status, response.statusText, errorMessage);
            return;
        }
        
        const data = await response.json();
        // Do something with the returned data here...

    } finally {
        abortController = null;
        stopButton.disabled = true;
        startButton.disabled = false;
    }
});

stopButton.addEventListener("click", (e) => {
    e.preventDefault();
    abortController?.abort();
});
Fetch API - Web APIs | MDN[^]

The CancellationToken passed in to the action should automatically be cancelled if the client aborts the request.
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900