在C#开发中,网络资源下载是非常常见的需求,无论是下载单张网络图片,还是实现大文件的多线程下载,都可以通过.NET内置的网络和线程相关API高效实现。多线程下载通过将文件分成多个分片,同时启动多个线程下载不同分片,最后合并分片得到完整文件,能大幅提升下载速度,还能支持断点续传功能。

C#下载单张网络图片的实现
下载单张网络图片是最基础的需求,我们可以使用HttpClient发送HTTP请求获取图片的字节流,再写入本地文件。下面是具体的实现代码:
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
class ImageDownloader
{
// 下载单张网络图片的方法
public static async Task DownloadSingleImageAsync(string imageUrl, string savePath)
{
try
{
// 创建HttpClient实例
using (HttpClient client = new HttpClient())
{
// 发送GET请求获取图片字节流
byte[] imageBytes = await client.GetByteArrayAsync(imageUrl);
// 将字节流写入本地文件
await File.WriteAllBytesAsync(savePath, imageBytes);
Console.WriteLine($"图片下载完成,保存路径:{savePath}");
}
}
catch (Exception ex)
{
Console.WriteLine($"图片下载失败:{ex.Message}");
}
}
}
// 调用示例
class Program
{
static async Task Main(string[] args)
{
string imageUrl = "http://ipipp.com/test.jpg";
string savePath = "D:/test_image.jpg";
await ImageDownloader.DownloadSingleImageAsync(imageUrl, savePath);
}
}
多线程文件下载器的核心思路
多线程文件下载器的实现需要解决几个核心问题:首先是获取文件的总大小,然后计算每个分片的起始和结束位置;接着启动多个线程分别下载对应的分片,保存到临时文件;最后所有分片下载完成后,合并临时文件得到完整的下载文件。如果需要支持断点续传,还需要记录每个分片的下载进度,下次启动时跳过已下载的部分。
1. 获取文件总大小
我们可以通过发送HEAD请求,获取响应头中的Content-Length字段,得到文件的总字节数。下面是获取文件大小的代码:
using System;
using System.Net.Http;
class FileSizeHelper
{
public static async Task<long> GetFileTotalSizeAsync(string fileUrl)
{
try
{
using (HttpClient client = new HttpClient())
{
// 发送HEAD请求,只获取响应头不获取响应体
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, fileUrl));
if (response.IsSuccessStatusCode)
{
// 获取Content-Length头,得到文件总大小
if (response.Content.Headers.ContentLength.HasValue)
{
return response.Content.Headers.ContentLength.Value;
}
}
return 0;
}
}
catch (Exception ex)
{
Console.WriteLine($"获取文件大小失败:{ex.Message}");
return 0;
}
}
}
2. 分片计算与多线程下载
假设我们将文件分成4个分片,每个分片的大小为总大小除以分片数,最后一个分片的大小可能需要调整,避免超出文件总大小。每个线程下载对应分片的时候,需要在请求头中添加Range字段,指定下载的字节范围。下面是分片下载的核心代码:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
class MultiThreadDownloader
{
// 分片信息类
class ChunkInfo
{
public int Index { get; set; } // 分片索引
public long Start { get; set; } // 分片起始字节
public long End { get; set; } // 分片结束字节
public string TempPath { get; set; } // 临时分片文件路径
}
/// <summary>
/// 多线程下载文件
/// </summary>
/// <param name="fileUrl">文件下载地址</param>
/// <param name="savePath">最终文件保存路径</param>
/// <param name="threadCount">线程数量</param>
public static async Task DownloadFileMultiThreadAsync(string fileUrl, string savePath, int threadCount = 4)
{
// 1. 获取文件总大小
long totalSize = await FileSizeHelper.GetFileTotalSizeAsync(fileUrl);
if (totalSize == 0)
{
Console.WriteLine("无法获取文件大小,下载终止");
return;
}
// 2. 计算每个分片的范围
List<ChunkInfo> chunks = new List<ChunkInfo>();
long chunkSize = totalSize / threadCount;
for (int i = 0; i < threadCount; i++)
{
ChunkInfo chunk = new ChunkInfo
{
Index = i,
Start = i * chunkSize,
End = (i == threadCount - 1) ? totalSize - 1 : (i + 1) * chunkSize - 1,
TempPath = $"{savePath}.part{i}"
};
chunks.Add(chunk);
}
// 3. 启动多线程下载每个分片
List<Task> downloadTasks = new List<Task>();
foreach (var chunk in chunks)
{
downloadTasks.Add(DownloadChunkAsync(fileUrl, chunk));
}
// 等待所有分片下载完成
await Task.WhenAll(downloadTasks);
// 4. 合并所有分片文件
using (FileStream finalStream = new FileStream(savePath, FileMode.Create))
{
foreach (var chunk in chunks)
{
using (FileStream chunkStream = new FileStream(chunk.TempPath, FileMode.Open))
{
await chunkStream.CopyToAsync(finalStream);
}
// 删除临时分片文件
File.Delete(chunk.TempPath);
}
}
Console.WriteLine($"文件下载完成,保存路径:{savePath}");
}
// 下载单个分片的方法
static async Task DownloadChunkAsync(string fileUrl, ChunkInfo chunk)
{
try
{
using (HttpClient client = new HttpClient())
{
// 添加Range请求头,指定下载的字节范围
var request = new HttpRequestMessage(HttpMethod.Get, fileUrl);
request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(chunk.Start, chunk.End);
// 发送请求获取分片内容
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
byte[] chunkData = await response.Content.ReadAsByteArrayAsync();
// 写入临时分片文件
await File.WriteAllBytesAsync(chunk.TempPath, chunkData);
Console.WriteLine($"分片{chunk.Index}下载完成,大小:{chunkData.Length}字节");
}
}
catch (Exception ex)
{
Console.WriteLine($"分片{chunk.Index}下载失败:{ex.Message}");
}
}
}
// 调用示例
class Program
{
static async Task Main(string[] args)
{
string fileUrl = "http://ipipp.com/largefile.zip";
string savePath = "D:/largefile.zip";
await MultiThreadDownloader.DownloadFileMultiThreadAsync(fileUrl, savePath, 4);
}
}
注意事项
- 使用
HttpClient时建议复用实例,避免频繁创建销毁带来性能损耗,也可以使用IHttpClientFactory管理实例。 - 如果下载的文件较大,建议添加下载进度回调,方便上层展示下载进度。
- 实际开发中可以根据需要添加断点续传功能,下载前检查临时分片文件是否存在,若存在则跳过已下载部分。
- 多线程数量不是越多越好,需要根据网络情况和服务器限制合理设置,一般4到8个线程即可。
C#多线程文件下载HttpClientTask文件流操作修改时间:2026-06-30 01:03:47