WebClient.CancelAsync – 文件仍在下载

我正在尝试使用Asp.NET Core创建一个Web API,它公开路由以启动和取消大文件的长下载。 服务器应该能够同时处理多个下载。

  • 使用WebClient.DownloadFileAsync执行下载,以便缩短响应时间并返回downloadId供以后使用。 WebClient的实例作为值存储在静态字典中,其对应的key自然是downloadId
  • 应通过访问与downloadId键对应的字典值检索的客户端实例上的WebClient.CancelAsync取消downloadId

以下代码在下载完成而未被取消时完美运行; 正确调用AsyncCompletedEventHandler(在这种情况下为OnDownloadFileCompleted )。

问题 :当调用WebClient.CancelAsync ,文件继续下载,并且不会立即调用OnDownloadFileCompleted 。 WebClient似乎要等到下载完成后再调用处理程序。 但是,在这两种情况下,都正确设置了AsyncCompletedEventArgs.Canceled属性(例如,如果确实调用了WebClient.CancelAsync ,则为true

  • 我错过了什么吗?
  • 是否有更好/更智能的模式来处理WebAPI中的多个下载?

任何帮助将非常感谢!

DownloadController.cs

 [Route ("api/download")] public class DownloadController { private readonly DownloadService service; public DownloadController (DownloadService service) { this.service = service; } [Route ("start")] [HttpPost] public string Start ([FromForm] string fileUrl) => this.service.StartDownload (fileUrl); [Route ("cancel")] [HttpPost] public void Cancel ([FromForm] string downloadId) => this.service.CancelDownload (downloadId); } 

DownloadService.cs

 public class DownloadService { public string DOWNLOAD_FOLDER { get => "C:\\tmp"; } public static Dictionary DownloadClients = new Dictionary (); public string StartDownload (string fileUrl) { var downloadId = Guid.NewGuid ().ToString ("N"); DownloadClients[downloadId] = new WebClient (); DownloadClients[downloadId].DownloadFileCompleted += OnDownloadFileCompleted; DownloadClients[downloadId].DownloadFileAsync (new Uri (fileUrl), Path.Combine (DOWNLOAD_FOLDER, downloadId), downloadId); return downloadId; } public void CancelDownload (string downloadId) { if (DownloadClients.TryGetValue (downloadId, out WebClient client)) { client.CancelAsync (); } } private void OnDownloadFileCompleted (object sender, AsyncCompletedEventArgs e) { var downloadId = e.UserState.ToString (); if (!e.Cancelled) { Debug.WriteLine ("Completed"); } else { Debug.WriteLine ("Cancelled"); //will only be reached when the file finishes downloading } if (DownloadClients.ContainsKey (downloadId)) { DownloadClients[downloadId].Dispose (); DownloadClients.Remove (downloadId); } } } 

我能够复制你看到的内容: CancelAsync实际上并没有取消下载。

使用HttpClient ,您可以使用CopyToAsync获取流并将其保存到文件中,接受CancellationToken 。 取消令牌会立即停止下载。

这是我修改为使用HttpClientDownloadService类。

 public class DownloadService { public string DOWNLOAD_FOLDER { get => "C:\\tmp"; } public static readonly ConcurrentDictionary Downloads = new ConcurrentDictionary(); public async Task StartDownload(string fileUrl) { var downloadId = Guid.NewGuid().ToString("N"); Downloads[downloadId] = new Download(fileUrl); await Downloads[downloadId].Start(Path.Combine(DOWNLOAD_FOLDER, downloadId)); return downloadId; } public void CancelDownload(string downloadId) { if (Downloads.TryRemove(downloadId, out var download)) { download.Cancel(); } } 

这使用了如下所示的Download类:

 public class Download { private static readonly HttpClient Client = new HttpClient(); private readonly string _fileUrl; private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); private Task _copyTask; private Stream _responseStream; private Stream _fileStream; public Download(string fileUrl) { _fileUrl = fileUrl; } public async Task Start(string saveTo) { var response = await Client.GetAsync(_fileUrl, HttpCompletionOption.ResponseHeadersRead); _responseStream = await response.Content.ReadAsStreamAsync(); _fileStream = File.Create(saveTo); _copyTask = _responseStream.CopyToAsync(_fileStream, 81920, _tokenSource.Token).ContinueWith(task => { if (task.IsCanceled) return; _responseStream.Dispose(); _fileStream.Dispose(); }); } public void Cancel() { _tokenSource.Cancel(); _responseStream.Dispose(); _fileStream.Dispose(); } } 

您仍然需要做一些工作才能从“ Downloads列表中删除成功完成的下载,但我会将其留给您。