You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何CancellationToken无法中途取消WebClient文件下载?

问题:CancellationToken无法中途取消WebClient下载的原因分析

我正在下载一个1GB的文件,尝试按'c'触发取消操作。如果在下载开始前快速按下'c',取消可以生效;但下载启动后,程序会下载完整文件,仅在下载结束后将Cancelled标记设为true。为什么CancellationToken无法中途取消下载?

代码示例

using System.Net;

Console.WriteLine("Hello, World!");
Console.WriteLine("Downloading a 100MB file...");


var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;

DownloadFileWithToken(cancellationToken);



Console.WriteLine("Press C, to cancel the download");
if (Console.ReadKey().KeyChar == 'c')
{
    cancellationTokenSource.Cancel();
    Console.WriteLine("\n\rCancellation Sent");
}


Console.ReadLine();


 void DownloadFileWithToken(CancellationToken token)
{
    const string file1Gb = "https://speed.hetzner.de/1GB.bin";

    var webClient = new WebClient();

    token.Register(() => webClient.CancelAsync());
    webClient.DownloadProgressChanged += (sender, e) =>
    {
        Console.WriteLine(e.BytesReceived);
    };

    webClient.DownloadDataCompleted += (sender, e) =>
    {


        if (!e.Cancelled)
        {
            byte[] data = (byte[])e.Result;
            Console.WriteLine($"\nDownload Completed: {data.Length}");
        }
        else
        {

            Console.WriteLine("\nDownload canceled");
        }
    };

    webClient.DownloadDataAsync(new Uri(file1Gb));

}

场景示例

  • 快速取消示例:快速取消
  • 下载启动后尝试取消示例:下载启动后尝试取消
  • 下载完成后才标记取消示例:下载完成后才标记取消

问题根源

  1. WebClient的设计局限性:WebClient的异步方法基于旧的APM(异步编程模型)实现,它的CancelAsync()并非真正即时取消——只是设置内部标记,只有当下载操作到达下一个“检查点”时才会终止。但大文件下载时,WebClient会持续接收数据直到缓冲区填满或请求完成,不会主动中断TCP连接。
  2. 代码执行时序问题:代码中先启动下载,再进入等待用户输入的阻塞逻辑。当用户按下'c'时,下载已进行一段时间,WebClient的CancelAsync()无法中断正在进行的数据接收流程。

解决方法

改用现代的HttpClient配合CancellationToken,它支持真正的即时取消,会直接中断底层HTTP请求:

using System.Net.Http;
using System.Threading;

Console.WriteLine("Hello, World!");
Console.WriteLine("Downloading a 1GB file...");

var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;

// 启动下载任务
var downloadTask = DownloadFileWithToken(cancellationToken);

Console.WriteLine("Press C to cancel the download");
if (Console.ReadKey().KeyChar == 'c')
{
    cancellationTokenSource.Cancel();
    Console.WriteLine("\n取消指令已发送");
}

try
{
    await downloadTask;
}
catch (OperationCanceledException)
{
    Console.WriteLine("\n下载已取消");
}
catch (Exception ex)
{
    Console.WriteLine($"\n下载出错: {ex.Message}");
}

Console.ReadLine();

async Task DownloadFileWithToken(CancellationToken token)
{
    const string file1Gb = "https://speed.hetzner.de/1GB.bin";
    using var httpClient = new HttpClient();
    
    using var response = await httpClient.GetAsync(file1Gb, HttpCompletionOption.ResponseHeadersRead, token);
    response.EnsureSuccessStatusCode();
    
    var totalBytes = response.Content.Headers.ContentLength ?? -1;
    var receivedBytes = 0;
    
    using var stream = await response.Content.ReadAsStreamAsync(token);
    using var memoryStream = new MemoryStream();
    
    var buffer = new byte[8192];
    int bytesRead;
    while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
    {
        memoryStream.Write(buffer, 0, bytesRead);
        receivedBytes += bytesRead;
        Console.WriteLine($"已接收字节数: {receivedBytes}");
        
        // 主动检查取消标记,确保即时响应
        token.ThrowIfCancellationRequested();
    }
    
    Console.WriteLine($"\n下载完成: {memoryStream.Length} 字节");
}

关键改进点

  • HttpClient的异步方法配合CancellationToken可直接中断HTTP请求,实现真正的中途取消。
  • 使用HttpCompletionOption.ResponseHeadersRead,避免一次性加载整个文件到内存,同时允许更早响应取消指令。
  • 在流读取循环中主动调用token.ThrowIfCancellationRequested(),确保取消指令被即时处理。

内容的提问来源于stack exchange,提问作者José Alonso

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 19:17:51