WebClient.CancelAsync - 文件仍在下载

时间:2018-01-10 12:54:21

标签: c# asp.net .net asp.net-core webclient

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

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

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

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

  • 我错过了什么吗?
  • 是否有更好/更智能的模式来处理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<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);
        }
    }
}

1 个答案:

答案 0 :(得分:2)

我能够复制您所看到的内容:CancelAsync实际上并未取消下载。

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

以下是我修改为使用DownloadService的{​​{1}}课程。

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(); } } 列表中删除已成功完成的下载,您仍需要完成一些工作,但我会将其留给您。