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

如何快速下载千余文件?优化C#下载代码兼顾速度与资源占用

优化文件下载速度:低资源占用的多线程方案

嘿,我懂你的需求——想加快图片/MP4文件的下载速度,但又不想过度占用CPU、带宽这些系统资源,毕竟没人想让电脑卡到没法用对吧?咱们来一步步优化你的现有代码,找到平衡速度和资源占用的最优方案。

首先得说:你原来用WebClient单线程循环下载的方式,每次只能处理一个URL,速度慢是必然的。而且WebClient现在算是比较旧的API了,更推荐用HttpClient——它能复用连接池,性能更好,也更适合多场景的下载需求。

下面给你两个实用的方案,都是可控并发的思路(核心就是限制同时下载的数量,避免资源过载):

方案一:异步+SemaphoreSlim(推荐,低资源占用)

这个方案用异步IO操作配合信号量精准控制并发数,线程利用率高,CPU占用极低(因为大部分时间都在等网络响应,不会阻塞线程),非常适合IO密集型的下载场景。

// 复用HttpClient实例,不要在循环里创建!
private static readonly HttpClient _httpClient = new HttpClient();
private static SemaphoreSlim _downloadSemaphore;

/// <summary>
/// 异步批量下载文件,可控制最大并发数
/// </summary>
/// <param name="saveLocation">文件保存目录</param>
/// <param name="urls">待下载的URL列表</param>
/// <param name="maxConcurrentDownloads">最大同时下载数,默认3,可根据带宽调整</param>
public static async Task DownloadAsync(string saveLocation, List<string> urls, int maxConcurrentDownloads = 3)
{
    // 初始化信号量,限制并发数
    _downloadSemaphore = new SemaphoreSlim(maxConcurrentDownloads);
    var downloadTasks = new List<Task>();

    // 确保保存目录存在
    Directory.CreateDirectory(saveLocation);

    foreach (var url in urls)
    {
        // 等待信号量,获取下载权限
        await _downloadSemaphore.WaitAsync();
        // 添加下载任务到列表
        downloadTasks.Add(DownloadSingleFileAsync(url, saveLocation));
    }

    // 等待所有下载任务完成
    await Task.WhenAll(downloadTasks);
}

private static async Task DownloadSingleFileAsync(string url, string saveLocation)
{
    try
    {
        // 从URL中提取文件名
        var fileName = Path.GetFileName(url);
        var savePath = Path.Combine(saveLocation, fileName);

        // 异步获取文件流,异步写入本地文件
        using (var responseStream = await _httpClient.GetStreamAsync(url))
        using (var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, useAsync: true))
        {
            await responseStream.CopyToAsync(fileStream);
        }
        Console.WriteLine($"✅ 下载完成: {fileName}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"❌ 下载失败 {url}: {ex.Message}");
    }
    finally
    {
        // 释放信号量,让其他任务可以开始下载
        _downloadSemaphore.Release();
    }
}

这个方案的优点:

  • 资源占用低:异步IO不会阻塞线程,CPU使用率不会飙升
  • 精准控并发:通过maxConcurrentDownloads可以灵活调整同时下载的数量(比如从2开始测试,慢慢调到不影响系统流畅的最大值)
  • 性能更优:复用HttpClient实例,避免频繁创建销毁连接池资源,减少网络开销

方案二:Parallel.ForEach(简单易改)

如果你不想改成异步方法,或者希望代码改动最小,这个方案更适合。它用Parallel.ForEach实现并行下载,同时通过MaxDegreeOfParallelism限制并发数。

private static readonly HttpClient _httpClient = new HttpClient();

/// <summary>
/// 并行批量下载文件,控制最大并行数
/// </summary>
/// <param name="saveLocation">文件保存目录</param>
/// <param name="urls">待下载的URL列表</param>
/// <param name="maxDegreeOfParallelism">最大并行数,默认3</param>
public static void DownloadParallel(string saveLocation, List<string> urls, int maxDegreeOfParallelism = 3)
{
    Directory.CreateDirectory(saveLocation);
    // 设置并行选项,限制最大并行数
    var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism };

    Parallel.ForEach(urls, parallelOptions, url =>
    {
        try
        {
            var fileName = Path.GetFileName(url);
            var savePath = Path.Combine(saveLocation, fileName);

            // 同步获取流并写入文件(因为Parallel.ForEach不直接支持异步lambda)
            using (var responseStream = _httpClient.GetStreamAsync(url).Result)
            using (var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write))
            {
                responseStream.CopyTo(fileStream);
            }
            Console.WriteLine($"✅ 下载完成: {fileName}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"❌ 下载失败 {url}: {ex.Message}");
        }
    });
}

这个方案的特点:

  • 代码改动小:和你原来的循环结构类似,容易上手
  • 快速见效:比单线程下载快很多,同时不会无限制占用资源
  • 注意:这里用了.Result同步等待网络请求,相比异步方案,线程占用会稍高一些,但对于你的需求来说完全够用

一些关键注意事项

  • 一定要复用HttpClient:绝对不要在循环里创建HttpClient实例,否则会耗尽系统Socket资源,导致下载失败或报错
  • 调整并发数:根据你的带宽和设备性能调整maxConcurrentDownloads或maxDegreeOfParallelism的值——比如带宽小就设2-3,带宽大可以调到5-8,找到最适合你的平衡点
  • 异常处理:代码里已经加了基础的异常捕获,你可以根据需求扩展(比如重试机制、日志记录)
  • 超时设置:可以给HttpClient加超时限制,避免某个下载任务一直卡住:_httpClient.Timeout = TimeSpan.FromMinutes(5);

内容的提问来源于stack exchange,提问作者user5405648

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:01:26