C#如何更高效从API批量下载约45000张单张50kb以下的图片
优化方案说明
现有代码的性能瓶颈
- 并发请求限制过低:.NET框架默认对同一域名的HTTP并发连接数限制为2~4,哪怕你用
Parallel.ForEach开再多线程,大部分线程都在等连接释放,速度根本上不去 - WebClient资源开销大:每次请求都新建
WebClient实例,反复建立销毁连接,额外增加了大量握手开销 - 多余的IO与CPU操作:下载的二进制本身就是标准JPG文件,你把它读到
MemoryStream转成Image对象再存回磁盘,属于完全无意义的编解码操作,白白浪费CPU和内存资源
具体优化步骤
- 全局调整HTTP并发连接限制,在程序初始化阶段添加如下代码,数值可根据自己的网络带宽和对方接口的限流规则调整,一般设置为32~128即可:
// 针对你的API域名调整并发限制,也可以全局设置DefaultConnectionLimit ServicePointManager.FindServicePoint(new Uri("http://url.com")).ConnectionLimit = 64;
- 复用
HttpClient实例,避免每次请求新建HTTP客户端,HttpClient天生线程安全,全局只需初始化一次 - 直接写入二进制文件,跳过多余的图片编解码步骤
- 显式控制并行度,避免线程过多导致的上下文切换开销
优化后代码示例
private static readonly string baseUrl = "http://url.com/Handlers/Image.ashx?imageid={0}&type=image"; // 全局复用一个HttpClient实例 private static readonly HttpClient _httpClient = new HttpClient(); internal static void DownloadAllMissingPictures(List<ListObject> ImagesToDownload, string imageFolderPath) { // 提前创建目录避免多线程下重复创建的问题 if (!Directory.Exists(imageFolderPath)) { Directory.CreateDirectory(imageFolderPath); } // 显式设置并行度,和前面设置的连接限制作匹配 Parallel.ForEach(ImagesToDownload, new ParallelOptions { MaxDegreeOfParallelism = 64 }, imageItem => { try { string url = string.Format(baseUrl, imageItem.ImageId); string filePath = Path.Combine(imageFolderPath, $"{imageItem.ImageId}.jpg"); // 直接下载字节数组,不需要多余的转Image操作 byte[] imageData = _httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult(); // 直接写入文件 File.WriteAllBytes(filePath, imageData); } catch (Exception ex) { // 这里可以加你的错误日志逻辑,比如下载失败的imageId记录下来后续重试 Console.WriteLine($"下载图片{imageItem.ImageId}失败:{ex.Message}"); } }); }
额外优化建议
如果还要进一步提速,可以做批量重试逻辑,把第一次下载失败的图片收集起来,结束后再重试1~2次,避免单次网络波动导致的失败;如果对方接口支持HTTP/2的话,升级到.NET Core/.NET 5+版本,HTTP/2的多路复用能进一步提升下载效率。
内容的提问来源于stack exchange,提问作者Smutjes
相关产品推荐
相关产品推荐

