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

如何用HttpClient实现流畅下载进度与速度统计并解决卡顿问题?

问题解决方案

一、在ProgressChanged事件中实现下载统计(速度、已下载、总大小)

在HttpClientDownloadWithProgress类中维护状态变量,结合时间差计算下载速度,同时从响应头提取文件总大小:

  1. 添加类内状态字段:
    private long _lastBytesDownloaded;
    private DateTime _lastProgressTime;
    private long _totalBytes;
    
  2. 开始下载时获取文件总大小:
    var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
    _totalBytes = response.Content.Headers.ContentLength ?? -1; // 无法获取则标记为-1
    
  3. 读取流时计算进度与速度,控制更新频率避免UI频繁刷新:
    _lastProgressTime = DateTime.Now;
    _lastBytesDownloaded = 0;
    var buffer = new byte[32768]; // 32KB缓冲区,减少IO次数
    int bytesRead;
    
    while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
    {
        await fileStream.WriteAsync(buffer, 0, bytesRead);
        _downloadedBytes += bytesRead;
    
        var currentTime = DateTime.Now;
        var timeDiff = currentTime - _lastProgressTime;
        // 每100ms更新一次进度,平衡实时性与性能
        if (timeDiff.TotalMilliseconds >= 100)
        {
            var bytesDiff = _downloadedBytes - _lastBytesDownloaded;
            var speedBytesPerSec = bytesDiff / timeDiff.TotalSeconds;
            var speedKbPerSec = speedBytesPerSec / 1024;
            var speedMbPerSec = speedKbPerSec / 1024;
    
            ProgressChanged?.Invoke(new DownloadProgress
            {
                TotalBytes = _totalBytes,
                DownloadedBytes = _downloadedBytes,
                SpeedBytesPerSecond = speedBytesPerSec,
                SpeedKbPerSecond = speedKbPerSec,
                SpeedMbPerSecond = speedMbPerSec,
                ProgressPercentage = _totalBytes > 0 ? (double)_downloadedBytes / _totalBytes * 100 : -1
            });
    
            _lastBytesDownloaded = _downloadedBytes;
            _lastProgressTime = currentTime;
        }
    }
    // 下载完成后触发最后一次进度更新
    ProgressChanged?.Invoke(new DownloadProgress
    {
        TotalBytes = _totalBytes,
        DownloadedBytes = _downloadedBytes,
        SpeedBytesPerSecond = 0,
        ProgressPercentage = 100
    });
    
  4. 定义进度数据类:
    public class DownloadProgress
    {
        public long TotalBytes { get; set; }
        public long DownloadedBytes { get; set; }
        public double SpeedBytesPerSecond { get; set; }
        public double SpeedKbPerSecond { get; set; }
        public double SpeedMbPerSecond { get; set; }
        public double ProgressPercentage { get; set; }
    }
    

二、修复下载卡顿问题

浏览器下载流畅但代码卡顿,通常是以下原因导致,对应修复方案:

  • 缓冲区过小:将读取缓冲区从默认4KB改为32KB或64KB,减少IO交互次数(代码已在上一步体现)
  • 系统代理干扰:禁用自动代理并启用GZIP压缩:
    var handler = new HttpClientHandler
    {
        UseProxy = false,
        Proxy = null,
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
    };
    var httpClient = new HttpClient(handler);
    
  • TCP连接频繁重建:添加保持连接请求头:
    httpClient.DefaultRequestHeaders.Connection.Add("keep-alive");
    
  • UI线程阻塞:确保下载全程用await异步调用,绝对不要在UI线程使用Wait()或Result

三、修复进度条与Label不同步问题

进度更新事件在后台线程触发,直接操作UI控件会导致跨线程异常,必须切换到UI线程更新:

private void OnProgressChanged(DownloadProgress progress)
{
    if (InvokeRequired)
    {
        Invoke(new Action<DownloadProgress>(OnProgressChanged), progress);
        return;
    }

    // 更新进度条
    if (progress.TotalBytes > 0)
    {
        progressBar1.Maximum = (int)progress.TotalBytes;
        progressBar1.Value = (int)Math.Min(progress.DownloadedBytes, progress.TotalBytes); // 避免溢出
    }

    // 更新Label显示
    var downloadedSize = FormatFileSize(progress.DownloadedBytes);
    var totalSize = progress.TotalBytes > 0 ? FormatFileSize(progress.TotalBytes) : "未知";
    var speedText = progress.SpeedMbPerSecond > 1 
        ? $"{progress.SpeedMbPerSecond:F2} MB/s" 
        : $"{progress.SpeedKbPerSecond:F2} KB/s";
    label1.Text = $"已下载: {downloadedSize}/{totalSize} | 速度: {speedText}";
}

// 字节数转可读格式辅助方法
private string FormatFileSize(long bytes)
{
    return bytes switch
    {
        < 1024 => $"{bytes} B",
        < 1024 * 1024 => $"{bytes / 1024:F2} KB",
        < 1024 * 1024 * 1024 => $"{bytes / (1024 * 1024):F2} MB",
        _ => $"{bytes / (1024 * 1024 * 1024):F2} GB"
    };
}

额外注意事项

  • 不要过于频繁触发进度更新(建议100-200ms一次),避免UI线程过载
  • 处理Content-Length为null的情况(如动态生成文件),此时进度百分比显示为"未知"即可
  • 复用HttpClient实例,不要每次下载都创建新实例,避免端口耗尽

内容的提问来源于stack exchange,提问作者Daniel Lip

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 11:36:28