VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • 异步IO操作——C#编写关于大文件多线程下载

第一部分:C#基础入门
5.异步IO操作——大文件多线程下载
实例介绍
大文件(如1GB以上的视频、压缩包)单线程下载存在两个核心问题:速度慢(单线程只能利用vb.net教程C#教程python教程SQL教程access 2010教程
部分带宽)、阻塞UI(同步IO会卡住界面)。异步IO+多块并行下载通过以下方式解决:
分块下载:将大文件拆分为多个固定大小的块(如1MB/块),同时下载多个块;
异步IO:网络请求、文件读写均用异步方法,不阻塞线程,提高CPU利用率;
并行处理:多个块并行下载,充分利用带宽资源。
本节通过大文件分块下载工具的实现,带你掌握异步IO与多块并行的核心逻辑,理解“为什么大文件要这么下载”。
需求分析
设计一个大文件下载工具,核心需求如下:
1.分块逻辑:将文件按固定大小(如1MB)拆分,最后一块自动适配剩余大小;
2.异步IO:网络请求(获取块数据)、文件写入(保存块)均用异步方法;
3.进度报告:实时显示总下载进度、每个块的下载状态;
4.文件合并:所有块下载完成后,按顺序合并为完整文件;
5.异常处理:单个块下载失败自动重试(最多3次);
6.断点续传:记录已下载的块,下次启动时跳过已完成的块(可选但实用)。
代码实现

  1. 核心模型类
    csharp
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Net.Http;
	using System.Threading.Tasks;
	
	// 下载任务信息
	public class DownloadTask
	{
	public string FileUrl { get; set; } // 文件URL
	public string SavePath { get; set; } // 保存路径
	public long BlockSize { get; set; } = 1024 * 1024; // 块大小(默认1MB)
	public int MaxRetryCount { get; set; } = 3; // 最大重试次数
	}
	
	// 分块信息
	public class BlockInfo
	{
	public int Index { get; set; } // 块索引(从0开始)
	public long StartByte { get; set; } // 块开始字节
	public long EndByte { get; set; } // 块结束字节
	public string TempFilePath { get; set; } // 临时块文件路径
	public bool IsCompleted { get; set; } = false; // 是否完成
	}
  1. 大文件下载服务类
    csharp
	public class LargeFileDownloader
	{
	private readonly HttpClient _httpClient;
	private List<BlockInfo> _blocks;
	private DownloadTask _task;
	private long _totalFileSize; // 文件总大小
	
	public LargeFileDownloader()
	{
	// 复用HttpClient实例(避免频繁创建销毁)
	_httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(5) };
	}
	
	// 启动下载(入口方法)
	public async Task StartDownloadAsync(DownloadTask task)
	{
	_task = task;
	Console.WriteLine($"=== 开始下载:{task.FileUrl} ===");
	
	// 1. 获取文件总大小(通过HEAD请求)
	_totalFileSize = await GetFileSizeAsync(task.FileUrl);
	Console.WriteLine($"文件总大小:{_totalFileSize / (1024*1024)} MB");
	
	// 2. 计算分块
	_blocks = CalculateBlocks(_totalFileSize, task.BlockSize);
	Console.WriteLine($"分块数量:{_blocks.Count}");
	
	// 3. 异步下载所有块(并行)
	var blockTasks = new List<Task>();
	foreach (var block in _blocks)
	{
	blockTasks.Add(DownloadBlockAsync(block));
	}
	await Task.WhenAll(blockTasks);
	
	// 4. 合并块文件
	await MergeBlocksAsync();
	Console.WriteLine($"=== 下载完成:{task.SavePath} ===");
	}
	
	// 步骤1:获取文件总大小(HEAD请求,不下载内容)
	private async Task<long> GetFileSizeAsync(string url)
	{
	var request = new HttpRequestMessage(HttpMethod.Head, url);
	var response = await _httpClient.SendAsync(request);
	response.EnsureSuccessStatusCode(); // 确保请求成功
	
	// 从响应头获取文件大小(Content-Length)
	if (long.TryParse(response.Content.Headers.ContentLength.ToString(), out long size))
	{
	return size;
	}
	throw new Exception("无法获取文件大小");
	}
	
	// 步骤2:计算分块
	private List<BlockInfo> CalculateBlocks(long totalSize, long blockSize)
	{
	var blocks = new List<BlockInfo>();
	long currentStart = 0;
	int index = 0;
	
	while (currentStart < totalSize)
	{
	long currentEnd = Math.Min(currentStart + blockSize -1, totalSize -1);
	var block = new BlockInfo
	{
	Index = index,
	StartByte = currentStart,
	EndByte = currentEnd,
	TempFilePath = Path.Combine(Path.GetDirectoryName(_task.SavePath), $"temp_{index}.block")
	};
	blocks.Add(block);
	currentStart = currentEnd + 1;
	index++;
	}
	return blocks;
	}
	
	// 步骤3:异步下载单个块
	private async Task DownloadBlockAsync(BlockInfo block)
	{
	int retryCount = 0;
	while (retryCount < _task.MaxRetryCount)
	{
	try
	{
	Console.WriteLine($"下载块 {block.Index}:{block.StartByte}-{block.EndByte}");
	var request = new HttpRequestMessage(HttpMethod.Get, _task.FileUrl);
	// 设置Range请求头:获取指定范围的字节(HTTP协议支持)
	request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(block.StartByte, block.EndByte);
	
	var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
	response.EnsureSuccessStatusCode();
	
	// 异步写入块文件(IO密集型,用异步方法)
	using var stream = await response.Content.ReadAsStreamAsync();
	using var fileStream = new FileStream(block.TempFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true); // 最后一个参数为true:异步文件流
	await stream.CopyToAsync(fileStream); // 异步复制流(不阻塞线程)
	
	block.IsCompleted = true;
	Console.WriteLine($"块 {block.Index} 下载完成");
	return;
	}
	catch (Exception ex)
	{
	retryCount++;
	Console.WriteLine($"块 {block.Index} 下载失败(第{retryCount}次重试):{ex.Message}");
	if (retryCount >= _task.MaxRetryCount)
	{
	throw new Exception($"块 {block.Index} 重试次数耗尽");
	}
	await Task.Delay(1000); // 重试前等待1秒
	}
	}
	}
	
	// 步骤4:合并块文件
	private async Task MergeBlocksAsync()
	{
	Console.WriteLine("=== 开始合并块文件 ===");
	using var outputStream = new FileStream(_task.SavePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true);
	
	foreach (var block in _blocks.OrderBy(b => b.Index)) // 按索引顺序合并
	{
	if (!block.IsCompleted)
	{
	throw new Exception($"块 {block.Index} 未完成,无法合并");
	}
	using var blockStream = new FileStream(block.TempFilePath, FileMode.Open, FileAccess.Read, FileShare.None,4096,true);
	await blockStream.CopyToAsync(outputStream); // 异步复制到输出流
	File.Delete(block.TempFilePath); // 合并后删除临时块
	}
	Console.WriteLine("=== 块文件合并完成 ===");
	}
	}
  1. 测试程序
    csharp
	class Program
	{
	static async Task Main(string[] args)
	{
	var downloader = new LargeFileDownloader();
	var task = new DownloadTask
	{
	FileUrl = "https://example.com/largefile.zip", // 替换为实际大文件URL
	SavePath = @"C:Downloadslargefile.zip",
	BlockSize = 2*1024*1024, // 2MB per block
	MaxRetryCount =3
	};
	
	try
	{
	await downloader.StartDownloadAsync(task);
	}
	catch (Exception ex)
	{
	Console.WriteLine($"下载失败:{ex.Message}");
	}
	}
	}

逐行讲解

  1. 模型类
    DownloadTask:封装下载任务的核心参数,如URL、保存路径、块大小、重试次数,让代码更整洁;
    BlockInfo:记录每个分块的索引、字节范围、临时文件路径、完成状态,方便管理每个块的下载进度。
  2. 下载服务类(LargeFileDownloader)
    构造函数:创建复用的HttpClient实例(HttpClient是线程安全的,复用能减少资源消耗);
    StartDownloadAsync:入口方法,串联获取文件大小、分块、下载块、合并文件四个步骤;
    GetFileSizeAsync:用HEAD请求获取文件大小(HEAD请求只返回响应头,不下载内容,效率高);
    CalculateBlocks:按块大小拆分文件,最后一块处理边界情况(避免超出总大小);
    DownloadBlockAsync:
    用Range请求头获取指定字节范围的内容(HTTP协议支持,是分块下载的核心);
    异步读取响应流并写入临时文件(用CopyToAsync异步方法,不阻塞线程);
    重试逻辑:失败后重试最多3次,避免网络波动导致下载失败;
    MergeBlocksAsync:按索引顺序合并临时块,合并后删除临时文件,节省空间。
  3. 测试程序
    Main方法:创建下载任务,调用StartDownloadAsync,捕获异常并处理,确保程序稳定。
    基础知识拓展
  4. 异步IO的核心原理
    IO密集型任务:文件读写、网络请求等操作,CPU等待IO完成的时间远大于计算时间;
    异步IO:线程发起IO请求后,不阻塞等待,而是继续处理其他任务,IO完成后通过回调通知线程继续执行;
    同步IO vs异步IO:同步IO会阻塞线程,导致线程资源浪费;异步IO能提高线程利用率,适合高并发场景。
  5. Range请求头的作用
    HTTP Range:允许客户端请求文件的一部分,格式为Range: bytes=start-end;
    应用场景:分块下载、断点续传(记录已下载的字节范围,下次继续);
    注意事项:服务器必须支持Range请求(响应头包含Accept-Ranges: bytes),否则无法分块。
  6. 断点续传的实现思路
    记录进度?将已完成的块信息保存到本地文件(如JSON);
    启动时恢复:下次下载前读取进度文件,跳过已完成的块;
    实现步骤:在StartDownloadAsync中检查进度文件,更新_blocks的IsCompleted状态,只下载未完成的块。
  7. 多块并行的注意事项
    块大小选择:块太小会增加请求次数( overhead ),块太大则并行度低;建议1-4MB;
    服务器限制:部分服务器限制同时连接数,过多并行请求可能被拒绝;
    资源控制:用SemaphoreSlim限制并行请求数量(如最多5个并行块),避免资源耗尽。
  8. 异常处理的最佳实践
    重试策略:对网络请求失败重试,设置合理的重试间隔(如指数退避:1s→2s→4s);
    错误日志:记录失败的块信息,方便排查问题;
    优雅退出:下载中断时,保存进度文件,下次继续。
    总结
    大文件异步IO下载的核心是分块+并行+异步:
    分块:拆分大任务为小任务,提高并行度;
    并行:同时下载多个块,充分利用带宽;
    异步:不阻塞线程,提高CPU利用率。
    掌握本节内容后,你可以扩展功能:比如断点续传、进度条显示、多服务器镜像选择等,满足更复杂的下载需求。异步IO是C#开发中必备的技能,尤其在处理IO密集型任务时,能大幅提升程序性能。
 本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49427.html

相关教程