HttpClient是下载文件的现代首选,需单例复用、设置超时、用ResponseHeadersRead避免内存溢出、分块读写并支持IProgress进度报告,同时处理重定向、认证、异常和完整性校验。

用 HttpClient 下载文件最稳妥
直接用 WebClient 虽然简单,但已标记为过时(.NET Core 5+ 开始不推荐),HttpClient 是现代首选。它支持异步、可复用、能控制超时和请求头,还天然适配 Progress 实时反馈进度。
关键点:
-
HttpClient应作为单例或静态实例重用,不要每次下载都 new 一个 - 务必用
await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead),避免把整个响应体提前读进内存 - 用
response.Content.ReadAsStreamAsync()获取流后,再配合FileStream分块写入磁盘 - 记得设置
client.Timeout,否则大文件可能卡死无响应
示例片段:
var client = new HttpClient();
client.Timeout = TimeSpan.FromMinutes(10);
using var response = await client.GetAsync("https://example.com/file.zip", HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
using var streamToReadFrom = await response.Content.ReadAsStreamAsync();
using var streamToWriteTo = File.Create(@"C:\temp\file.zip");
await streamToReadFrom.CopyToAsync(streamToWriteTo);
需要显示进度?绑定 IProgress 才行
HttpClient 本身不提供下载进度回调,得手动分块读取并报告。常见错误是试图在 CopyToAsync 里塞回调——它不支持。
正确做法是用循环 + streamToReadFrom.ReadAsync 读固定大小缓冲区,每读一块就调用 progress.Report():
- 缓冲区大小建议设为 8192 或 65536,太小影响性能,太大占内存
- 注意最后一批数据可能不足缓冲区长度,别直接用
buffer.Length当实际字节数 - 进度百分比要基于
response.Content.Headers.ContentLength计算,但该值可能为null(服务器未返回Content-Length)
若 ContentLength 缺失,只能用“已下载字节数”做相对进度,或改用 WebClient.DownloadFileAsync(仅限 .NET Framework 兼容场景)。
处理重定向和认证:别漏掉 AllowAutoRedirect 和 DefaultRequestHeaders
很多下载失败不是因为代码逻辑,而是服务器返回 302 或要求 Bearer Token。默认 HttpClient 会自动跟随重定向,但某些私有 API 可能禁用此行为。
- 如需手动控制跳转,设
client.DefaultRequestHeaders.Referrer或检查response.RequestMessage.RequestUri是否变化 - 带 Token 的请求:加
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token) - 需要 Cookie 或表单登录态?必须复用同一个
HttpClient实例,并启用CookieContainer(需搭配HttpHandler构造)
没设认证头却去请求受保护资源,大概率收到 401;忽略重定向则可能卡在中间页返回 HTML 而非文件流。
大文件或弱网下容易出错:必须加取消令牌和异常分类处理
用户点“取消下载”、网络中断、磁盘满——这些都会抛不同异常,不能全 catch 住吞掉。
- 所有
await调用都要传cancellationToken,并在 UI 层绑定按钮的取消逻辑 -
OperationCanceledException表示主动取消,不是错误,别记日志 -
HttpRequestException包含网络层问题(DNS 失败、连接被拒),可重试 1–2 次 -
IOException如磁盘空间不足,应提示用户清理而非重试
最容易被忽略的是:下载中途断电或进程被杀,会导致文件不完整却没报错。建议下载完成后用 response.Content.Headers.ContentMD5 校验(如果服务器提供了),或至少检查最终文件大小是否匹配 ContentLength。










