You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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}");
        }
    }
}

错误与中断处理细节

  1. 网络异常处理:捕获HttpRequestException处理HTTP状态码错误、连接超时等网络问题,确保异常不会扩散到其他任务。
  2. 文件操作异常:捕获IOException处理本地文件写入失败(如权限不足、磁盘满),避免因本地IO问题导致整个下载器崩溃。
  3. 用户取消处理:触发取消时,CancellationToken会中断GetAsync和CopyToAsync等异步操作,抛出OperationCanceledException,此时会删除未完成的文件,避免残留无效文件。
  4. 资源清理:在finally块中释放SemaphoreSlim的许可,确保即使任务失败或取消,许可也会被释放,不会导致其他任务永久等待。

内容的提问来源于stack exchange,提问作者Hoàng Tâm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 11:53:12