我正在尝试使用 Asp.NET Core 创建一个 Web API,该 API 公开启动和取消大文件长时间下载的路由。服务器应该能够同时处理多个下载。
- 下载是使用执行的
WebClient.DownloadFileAsync
为了有一个短的响应时间并返回downloadId
供以后使用。 WebClient 的实例作为值存储在静态字典中,其对应的key
自然是属于downloadId
.
- 应使用取消下载
WebClient.CancelAsync
在通过访问对应于字典的值来检索的客户端实例上downloadId
key.
当下载完成且没有被取消时,以下代码可以完美运行; AsyncCompletedEventHandler (OnDownloadFileCompleted
在这种情况下)被正确调用。
PROBLEM: 调用时WebClient.CancelAsync
,文件继续下载并且OnDownloadFileCompleted
不会立即调用。 WebClient 似乎要等到下载完成后再调用处理程序。然而,在这两种情况下,财产AsyncCompletedEventArgs.Canceled
已正确设置(例如true
if WebClient.CancelAsync
确实被调用了。
- 我错过了什么吗?
- 是否有更好/更智能的模式来处理 WebAPI 中的多个下载?
任何帮助将不胜感激!
下载Controller.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);
}
下载服务.cs
public class DownloadService {
public string DOWNLOAD_FOLDER { get => "C:\\tmp"; }
public static Dictionary<string, WebClient> DownloadClients = new Dictionary<string, WebClient> ();
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
实际上并没有取消下载。
Using HttpClient
,您可以使用以下命令获取流并将其保存到文件中CopyToAsync
,接受一个CancellationToken
。取消令牌会立即停止下载。
这里是DownloadService
我修改后使用的类HttpClient
.
public class DownloadService {
public string DOWNLOAD_FOLDER {
get => "C:\\tmp";
}
public static readonly ConcurrentDictionary<string, Download> Downloads = new ConcurrentDictionary<string, Download>();
public async Task<string> 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
清单,但我会把它留给你。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)