在C#开发桌面应用或后台服务时,实现文件下载功能的同时显示实时进度条,需要准确获取已下载的字节数,再结合文件总大小计算下载百分比,最后将进度同步到UI控件上。下面以HttpClient为基础,详细讲解完整的实现方案。

核心实现思路
整个流程可以分为三个步骤:首先发起HTTP请求获取文件的总大小,然后以流的方式读取下载内容,实时记录已下载的字节数,最后计算下载百分比并触发进度更新事件,由UI层监听事件更新进度条。
获取文件总大小
在下载开始前,先通过HTTP HEAD请求获取响应头中的Content-Length字段,得到文件的总字节数,用于后续进度计算。
using System;
using System.Net.Http;
public class FileDownloadHelper
{
private readonly HttpClient _httpClient;
public FileDownloadHelper()
{
_httpClient = new HttpClient();
}
/// <summary>
/// 获取文件总大小
/// </summary>
/// <param name="fileUrl">文件下载地址</param>
/// <returns>文件总字节数,获取失败返回-1</returns>
public async Task<long> GetFileTotalSizeAsync(string fileUrl)
{
try
{
// 发送HEAD请求,只获取响应头不获取响应体
var request = new HttpRequestMessage(HttpMethod.Head, fileUrl);
var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
// 从响应头获取Content-Length
if (response.Content.Headers.ContentLength.HasValue)
{
return response.Content.Headers.ContentLength.Value;
}
return -1;
}
catch (Exception ex)
{
Console.WriteLine($"获取文件大小失败:{ex.Message}");
return -1;
}
}
}
实现带进度的文件下载
接下来实现核心的下载方法,通过流读取的方式逐段获取下载内容,每读取一段就更新已下载字节数,计算进度并触发事件。
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public class FileDownloadHelper
{
private readonly HttpClient _httpClient;
// 定义进度更新委托,参数为已下载字节数、总字节数、下载百分比
public delegate void ProgressChangedHandler(long downloadedBytes, long totalBytes, double progressPercentage);
// 进度更新事件
public event ProgressChangedHandler OnProgressChanged;
public FileDownloadHelper()
{
_httpClient = new HttpClient();
}
/// <summary>
/// 下载文件并报告进度
/// </summary>
/// <param name="fileUrl">文件下载地址</param>
/// <param name="savePath">本地保存路径</param>
/// <param name="totalSize">文件总大小,可通过GetFileTotalSizeAsync获取</param>
/// <param name="cancellationToken">取消令牌</param>
public async Task DownloadFileWithProgressAsync(string fileUrl, string savePath, long totalSize, CancellationToken cancellationToken = default)
{
long downloadedBytes = 0;
// 发送GET请求,获取响应流
var response = await _httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
// 创建本地文件流
using (var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None))
using (var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken))
{
var buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
{
// 写入本地文件
await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
downloadedBytes += bytesRead;
// 计算下载百分比
double progressPercentage = totalSize > 0 ? (double)downloadedBytes / totalSize * 100 : 0;
// 触发进度更新事件
OnProgressChanged?.Invoke(downloadedBytes, totalSize, progressPercentage);
}
}
}
}
UI层进度条更新示例
WinForm场景
WinForm中需要在UI线程更新ProgressBar控件,注意跨线程访问控件的线程安全问题。
using System;
using System.Threading;
using System.Windows.Forms;
public partial class MainForm : Form
{
private FileDownloadHelper _downloadHelper;
private CancellationTokenSource _cts;
public MainForm()
{
InitializeComponent();
_downloadHelper = new FileDownloadHelper();
// 订阅进度更新事件
_downloadHelper.OnProgressChanged += DownloadHelper_OnProgressChanged;
}
private async void btnStartDownload_Click(object sender, EventArgs e)
{
string fileUrl = "https://ipipp.com/test/file.zip";
string savePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.zip");
_cts = new CancellationTokenSource();
try
{
// 先获取文件总大小
long totalSize = await _downloadHelper.GetFileTotalSizeAsync(fileUrl);
if (totalSize <= 0)
{
MessageBox.Show("无法获取文件大小,将不显示精确进度");
}
// 开始下载
await _downloadHelper.DownloadFileWithProgressAsync(fileUrl, savePath, totalSize, _cts.Token);
MessageBox.Show("下载完成");
}
catch (OperationCanceledException)
{
MessageBox.Show("下载已取消");
}
catch (Exception ex)
{
MessageBox.Show($"下载失败:{ex.Message}");
}
}
private void DownloadHelper_OnProgressChanged(long downloadedBytes, long totalBytes, double progressPercentage)
{
// 跨线程更新UI,使用Invoke
if (progressBar1.InvokeRequired)
{
progressBar1.Invoke(new Action<long, long, double>(DownloadHelper_OnProgressChanged), downloadedBytes, totalBytes, progressPercentage);
return;
}
// 更新进度条,progressBar1的Maximum设置为100
progressBar1.Value = (int)Math.Round(progressPercentage);
// 更新进度文本
lblProgress.Text = $"已下载:{downloadedBytes}/{totalBytes} 字节,进度:{progressPercentage:F2}%";
}
private void btnCancelDownload_Click(object sender, EventArgs e)
{
_cts?.Cancel();
}
}
WPF场景
WPF中可以使用Dispatcher更新UI,也可以结合MVVM模式通过属性绑定更新进度条。
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Windows;
public partial class MainWindow : Window, INotifyPropertyChanged
{
private FileDownloadHelper _downloadHelper;
private CancellationTokenSource _cts;
private double _downloadProgress;
public double DownloadProgress
{
get { return _downloadProgress; }
set
{
_downloadProgress = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
_downloadHelper = new FileDownloadHelper();
_downloadHelper.OnProgressChanged += DownloadHelper_OnProgressChanged;
}
private async void btnStartDownload_Click(object sender, RoutedEventArgs e)
{
string fileUrl = "https://ipipp.com/test/file.zip";
string savePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.zip");
_cts = new CancellationTokenSource();
try
{
long totalSize = await _downloadHelper.GetFileTotalSizeAsync(fileUrl);
await _downloadHelper.DownloadFileWithProgressAsync(fileUrl, savePath, totalSize, _cts.Token);
MessageBox.Show("下载完成");
}
catch (OperationCanceledException)
{
MessageBox.Show("下载已取消");
}
catch (Exception ex)
{
MessageBox.Show($"下载失败:{ex.Message}");
}
}
private void DownloadHelper_OnProgressChanged(long downloadedBytes, long totalBytes, double progressPercentage)
{
// WPF中使用Dispatcher更新UI
Dispatcher.Invoke(() =>
{
DownloadProgress = progressPercentage;
});
}
private void btnCancelDownload_Click(object sender, RoutedEventArgs e)
{
_cts?.Cancel();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
注意事项
- 如果服务器不支持HEAD请求或者没有返回
Content-Length,进度百分比会显示为0,此时可以只显示已下载字节数,不计算百分比。 - 下载大文件时建议设置合理的缓冲区大小,默认8KB是比较通用的选择,也可以根据网络情况调整。
- 取消下载时通过CancellationToken传递取消信号,避免资源泄漏。
- 异步方法中不要使用async void,除了UI事件处理方法,其他场景建议使用async Task返回。
C#文件下载下载进度获取进度条显示HttpClient异步编程修改时间:2026-07-01 05:24:22