C#多线程文件下载器实现及错误处理方案咨询
多线程文件下载器实现(C#)
以下是完整的多线程文件下载器实现,包含并发控制、错误处理和用户取消支持:
using System; using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Threading; using System.Threading.Tasks; public class FileDownloader { private readonly string[] _urls; private readonly string _outputFolder; private readonly int _maxThreads; private readonly HttpClient _httpClient; private readonly SemaphoreSlim _semaphore; private readonly CancellationTokenSource _cts; private readonly List<DownloadResult> _results = new(); // 记录每个下载任务的结果 public class DownloadResult { public string Url { get; set; } public string LocalPath { get; set; } public bool IsSuccess { get; set; } public string ErrorMessage { get; set; } public bool IsCancelled { get; set; } } public FileDownloader(string[] urls, string outputFolder, int maxThreads) { _urls = urls ?? throw new ArgumentNullException(nameof(urls)); _outputFolder = outputFolder ?? throw new ArgumentNullException(nameof(outputFolder)); _maxThreads = maxThreads > 0 ? maxThreads : throw new ArgumentOutOfRangeException(nameof(maxThreads)); // 复用HttpClient实例提升性能 _httpClient = new HttpClient(); _semaphore = new SemaphoreSlim(_maxThreads); _cts = new CancellationTokenSource(); // 确保输出文件夹存在 Directory.CreateDirectory(_outputFolder); } public async Task<List<DownloadResult>> StartDownloadAsync() { var tasks = new List<Task>(); foreach (var url in _urls) { tasks.Add(DownloadSingleFileAsync(url, _cts.Token)); } // 等待所有任务完成 await Task.WhenAll(tasks); return new List<DownloadResult>(_results); } // 取消所有下载任务 public void CancelAllDownloads() { _cts.Cancel(); } private async Task DownloadSingleFileAsync(string url, CancellationToken token) { var result = new DownloadResult { Url = url }; string localFilePath = Path.Combine(_outputFolder, Path.GetFileName(url)); result.LocalPath = localFilePath; try { // 等待获取并发许可 await _semaphore.WaitAsync(token); // 检查是否已取消 token.ThrowIfCancellationRequested(); // 发起下载请求(仅先读取响应头,避免一次性加载大文件到内存) using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); response.EnsureSuccessStatusCode(); // 触发HTTP错误状态码的异常 // 写入本地文件 using var fileStream = new FileStream(localFilePath, FileMode.Create, FileAccess.Write, FileShare.None); await response.Content.CopyToAsync(fileStream, token); result.IsSuccess = true; } catch (OperationCanceledException) { result.IsCancelled = true; result.ErrorMessage = "下载已取消"; // 删除未完成的文件 if (File.Exists(localFilePath)) File.Delete(localFilePath); } catch (HttpRequestException ex) { result.IsSuccess = false; result.ErrorMessage = $"网络错误: {ex.Message}"; } catch (IOException ex) { result.IsSuccess = false; result.ErrorMessage = $"文件操作错误: {ex.Message}"; } catch (Exception ex) { result.IsSuccess = false; result.ErrorMessage = $"未知错误: {ex.Message}"; } finally { // 释放并发许可,无论成功失败都要释放,避免死锁 _semaphore.Release(); _results.Add(result); } } }
关键设计说明
- 并发控制:使用
SemaphoreSlim严格限制同时运行的下载任务数量,避免线程过多导致的资源竞争和网络拥堵,替代直接使用ThreadPool的无控调度。 - 线程管理:基于
Task(而非原始线程)实现异步下载,既利用了线程池的优化,又能通过Task.WhenAll统一跟踪所有任务状态,比直接操作ThreadPool更易维护。 - 错误隔离:每个文件的下载逻辑独立捕获异常,单个任务失败不会影响其他任务执行,错误信息会被记录到
DownloadResult中。 - 用户取消:通过
CancellationTokenSource实现全局取消,触发取消时会中断所有未完成的下载,并清理未完成的临时文件。
使用示例
class Program { static async Task Main(string[] args) { var urls = new[] { "https://example.com/file1.zip", "https://example.com/file2.exe", "https://example.com/file3.pdf" }; var downloader = new FileDownloader(urls, "./downloads", maxThreads: 3); // 启动下载任务在后台运行 var downloadTask = downloader.StartDownloadAsync(); Console.WriteLine("下载中,按任意键取消..."); Console.ReadKey(); // 触发取消 downloader.CancelAllDownloads(); // 等待任务结束并获取结果 var results = await downloadTask; Console.WriteLine("\n下载结果:"); foreach (var result in results) { if (result.IsSuccess) Console.WriteLine($"✅ 成功: {result.Url} -> {result.LocalPath}"); else if (result.IsCancelled) Console.WriteLine($"⏹️ 取消: {result.Url}"); else Console.WriteLine($"❌ 失败: {result.Url} - {result.ErrorMessage}"); } } }
错误与中断处理细节
- 网络异常处理:捕获
HttpRequestException处理HTTP状态码错误、连接超时等网络问题,确保异常不会扩散到其他任务。 - 文件操作异常:捕获
IOException处理本地文件写入失败(如权限不足、磁盘满),避免因本地IO问题导致整个下载器崩溃。 - 用户取消处理:触发取消时,
CancellationToken会中断GetAsync和CopyToAsync等异步操作,抛出OperationCanceledException,此时会删除未完成的文件,避免残留无效文件。 - 资源清理:在
finally块中释放SemaphoreSlim的许可,确保即使任务失败或取消,许可也会被释放,不会导致其他任务永久等待。
内容的提问来源于stack exchange,提问作者Hoàng Tâm
相关产品推荐
相关产品推荐

