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
。 取消令牌会立即停止下载。
这是我修改为使用HttpClient
的DownloadService
类。
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
列表中删除成功完成的下载,但我会将其留给您。