WebClient.CancelAsync — 文件仍在下载

2024-01-01

我正在尝试使用 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(使用前将#替换为@)

WebClient.CancelAsync — 文件仍在下载 的相关文章

随机推荐

  • 如何从 pyodbc 结果行创建逗号分隔的字符串?

    我有一个存储在cursor rows 中的行结果集 这些行是从pyodbc cursor execute 命令返回的 解压这些数据并将其放入逗号分隔字符串列表 或解压到自定义对象中 的最快方法是什么 目前我正在做以下事情 cursor ex
  • 如何使用 Google Sheets API 添加超链接?

    我正在尝试编写一个 python 脚本来添加超链接到谷歌工作表 我正在为此使用谷歌API 通过搜索 我发现我需要向其余 api 传递 HYPERLINK 类型的消息 来自文档 https developers google com shee
  • 如何检查单选按钮

    我有以下 MySQL 表 id Title Windows Linux IDE GUI RAD 1 Software 1 1 0 1 0 0 2 Software 2 0 1 0 1 0 我想通过查询 mySQL 数据库来填充表单 到目前为
  • 使用 std::function 移动语义

    std function从右值引用提供构造函数 按照标准 移动的函数对象会发生什么 会不会是空的 这样再次调用就没有效果了 这个问题有太多的混乱 我会尽力把事情说清楚 本节描述 std 定义对象的移出状态 17 6 5 15 lib typ
  • ECMAScript 6 中的 Symbol.for(string)

    我花了一段时间 但我终于弄清楚了 ECMAScript 6 中符号的用途是什么 在将属性附加到共享对象 例如 HTML 元素 时避免名称冲突 如果您遇到同样的问题 我建议本文 https hacks mozilla org 2015 06
  • 基于 div 而非 screen 的条件 CSS

    所以我做了一些研究并且接近答案 但是我想要做的事情可能无法仅用 CSS 实现 我真的希望如此 最终目标是 如果 id 为 Primary 的元素的宽度为 915 或更小 则将一类 bricks 的元素的宽度设置为 90 html 片段 di
  • 从数据创建 CMSampleBufferRef

    我正在尝试从数据创建 CMSampleBuffer Ref 并尝试将其提供给 AVAssetWriter 但资产编写者未能根据数据创建电影 以下是创建 CMSampleBufferRef 的代码 CVImageBufferRef cvimg
  • 从 QList 填充 QVector

    我有一个 QList 和 QVector 我填充 Qlist 然后尝试复制到 QVector 奥维特有一个来自列表 http qt project org doc qt 4 8 qvector html fromList方法 但这不起作用
  • 链接到 jQuery 文件的最佳方式[重复]

    这个问题在这里已经有答案了 就网站性能 速度而言 链接到 jquery 是否更好 如下所示 或者将文件放在服务器上并从那里链接到它们 如下所示 这取决于谁拥有更快的服务器 对吗 有一些优点code jquery com 这很常见 如果用户访
  • 将十六进制字符串转换为十六进制整数

    我必须将十六进制字符串转换为十六进制整数 如下所示 color 0xFF00FF can be any color else defined by functions colorto 0xFF00FF copy of color but f
  • Google App Engine 502 具有“上游过早关闭连接”,但似乎无法到达我们的实例

    我们有一个提供 API 的 Node js App Engine 服务 很少 500 个请求中就有 1 个 向客户端返回 502 并从 Google Cloud Logging 的 nginx 日志中获取错误 upstream premat
  • 偏航、俯仰和滚转值延迟

    我正在 Windows Phone 7 1 中开发一个应用程序 我需要手机在 x 轴和 y 轴上的当前旋转度数 我尝试使用运动 API 并使用它提供的偏航俯仰和滚动的适当值 但它提供的值是延迟的 因为如果我移动手机太快并将其旋转到 90 度
  • SoapUI - 自动将自定义 SOAP 标头添加到传出请求中

    所以我想要做的是自动将 SOAP 标头添加到 SoapUI 中生成的每个请求中 因为我有数百个请求 手动执行此操作很烦人 可以说 这是我从 WSDL 生成的示例请求 如下所示
  • 简单的 NFC 代码不起作用?

    我正在尝试开始使用 NFC 编写应用程序 我只需要从一部手机向另一部手机发送一条仅包含简单字符串的 NDef 消息 我的传输活动 public class MainActivity extends Activity NfcAdapter m
  • 从 HBase shell 导出数据

    我正在尝试将数据从 HBase Shell 导出到我可以解析的文本文件 然后添加到 msysql 数据库 我目前正在使用以下命令 echo scan registration COLUMNS gt registration status h
  • CSS:Safari 的边距问题

    On the 我正在开发的网站 http like o potomo us 出于某种原因 Safari 的边距需要与 FF IE8 Chrome 和 Opera 不同吗 我有一个链接 我想将其排列在标签旁边 除了 Safari 需要 12
  • COUNTIF 范围内的唯一日期

    我正在努力寻找一种方法COUNTIF S 特定日期集之间 但仅获取唯一日期的计数 作为上下文 我连续几天跟踪仪表 我可以在同一天获得多个具有不同仪表值的实例 事实上 这种情况可能会破坏我的平均米数 天数 Data A3 A8700 是日期范
  • Matplotlib:从多个子图中抓取单个子图

    我有一个应用程序 其中有一个带有九个线图子图 3x3 的图形 我想让用户选择其中一个图表 并打开一个小的 wx Python 应用程序以允许编辑和缩放指定的子图阴谋 是否可以从选定的子图中获取所有信息 即轴标签 轴格式 线条 刻度大小 刻度
  • 检查任何正在运行的二进制文件是否是 32 位或 64 位

    可以检查应用程序包的二进制进程是否在 32 位或 64 位模式下运行 如下所示在这个问题中回答了 https stackoverflow com questions 7335245 macosx find out if a process
  • WebClient.CancelAsync — 文件仍在下载

    我正在尝试使用 Asp NET Core 创建一个 Web API 该 API 公开启动和取消大文件长时间下载的路由 服务器应该能够同时处理多个下载 下载是使用执行的WebClient DownloadFileAsync为了有一个短的响应时