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

批量下载25k个6字节小文件速度过慢,求优化方案

问题描述

我使用HttpClient尝试下载约25000个6字节大小的文件到内存中,以此生成对应的SHA256密钥。尽管已经实现了并行处理,但速度毫无提升——目前下载总计约5.5MB的文件耗时约15分钟,处理器和网络资源几乎未被利用,程序处于空闲等待状态。

相关代码

并行下载任务包装函数

SemaphoreSlim downloadConcurrencySemaphore = new SemaphoreSlim(40);
ConcurrentQueue<Task> Sha256Tasks = new ConcurrentQueue<Task>();
foreach (string url in urls)
{
    var t = Task.Run(async () =>
    {
        await downloadConcurrencySemaphore.WaitAsync();
        try
        {
            await uploadedFile.CalculateChecksum();
        }
        catch (Exception ex)
        {
            { } // breakpoint for debugging
        }
        finally
        {
            downloadConcurrencySemaphore.Release();
        }
    });
    Sha256Tasks.Enqueue(t);
}
Task.WaitAll(Sha256Tasks.ToArray());

CalculateChecksum方法(下载文件并生成SHA256摘要)

public async Task CalculateChecksum()
{ // is being called up to 40 times in parallel (connection limit)
    byte[] file = await API.DownloadClient.Download(URL);
    Sha256Sum = Sha256.GetSha256Sum(file);
}

DownloadClient类(并行下载实现)

internal static class DownloadClient
{
    static DownloadClient()
    {
        _Client = new HttpClient();
        ServicePointManager.DefaultConnectionLimit = 40;

        var handler = new HttpClientHandler();
        handler.ClientCertificateOptions = ClientCertificateOption.Manual;
        handler.ServerCertificateCustomValidationCallback =
            (httpRequestMessage, cert, cetChain, policyErrors) =>
            {
                return true;
            };
        handler.MaxConnectionsPerServer = 40;

        _Client = new HttpClient(handler);
    }
    private static HttpClient _Client;
    internal static async Task<byte[]> Download(string url)
    {
        HttpResponseMessage response = await _Client.GetAsync(url);
        response.EnsureSuccessStatusCode();
        System.Net.Http.HttpContent content = response.Content;
        byte[] file = await content.ReadAsByteArrayAsync();
        return file;
    }
}
优化方案

1. 修复HttpClient初始化冗余问题

在DownloadClient静态构造函数中,你先创建了一个默认HttpClient实例,随后又用handler重新创建了一个,前一个实例被无意义丢弃。同时ServicePointManager.DefaultConnectionLimit的设置应该放在HttpClient实例化之前,确保配置生效。

2. 移除不必要的Task.Run包装

CalculateChecksum本身就是异步方法,无需用Task.Run额外包裹(这会浪费线程池线程)。可以直接收集异步任务,用更简洁的方式等待全部完成:

var semaphore = new SemaphoreSlim(40);
var tasks = new List<Task>();

foreach (var url in urls)
{
    var targetFile = uploadedFile; // 对应你的uploadedFile实例
    tasks.Add(ProcessFileAsync(targetFile, semaphore));
}
await Task.WhenAll(tasks);

async Task ProcessFileAsync(UploadedFile file, SemaphoreSlim semaphore)
{
    await semaphore.WaitAsync();
    try
    {
        await file.CalculateChecksum();
    }
    catch (Exception ex)
    {
        // 替换空catch,记录异常便于排查问题
        Console.WriteLine($"处理文件失败: {ex.Message}");
    }
    finally
    {
        semaphore.Release();
    }
}

3. 边下载边计算哈希,减少内存开销

每个文件仅6字节,无需先把整个文件读到内存再计算哈希。可以直接通过流读取并实时计算SHA256,减少内存拷贝步骤:

// 修改CalculateChecksum方法
public async Task CalculateChecksum()
{
    using var sha256 = SHA256.Create();
    Sha256Sum = await API.DownloadClient.DownloadAndComputeSha256(URL, sha256);
}

// 在DownloadClient中添加新方法
internal static async Task<byte[]> DownloadAndComputeSha256(string url, SHA256 sha256)
{
    // 使用ResponseHeadersRead,避免等待全部内容下载完成再处理
    using var response = await _Client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
    response.EnsureSuccessStatusCode();
    using var stream = await response.Content.ReadAsStreamAsync();
    
    // 边读流边计算哈希
    await sha256.ComputeHashAsync(stream);
    return sha256.Hash;
}

4. 调整并发数并尝试HTTP/2

  • 并发数优化:40的并发数未必是最优值,小文件请求的TCP握手、等待响应开销占比极高,可以尝试调高并发数(如100、200),但需注意目标服务器是否有限流策略。
  • 启用HTTP/2:如果目标服务器支持HTTP/2,可配置HttpClientHandler启用该协议,多个请求可复用同一个TCP连接,大幅减少握手开销:
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback = (_, cert, _, _) => true;
handler.MaxConnectionsPerServer = 100;
// 启用HTTP/2
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
handler.HttpVersion = HttpVersion.Version20;
handler.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;

5. 排查网络与服务端限制

  • 程序空闲等待可能源于DNS解析延迟,可提前批量解析所有URL的IP地址,避免重复解析。
  • 确认目标服务器是否存在请求速率限制,如果有,可尝试联系服务端调整,或者改用批量请求接口(若API支持),减少总请求次数。

6. 移除空Catch块

空Catch会隐藏请求失败的异常,导致你无法排查潜在问题(比如大量请求失败拖慢整体速度),至少要添加日志记录逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 20:45:45