C#使用RestSharp异步请求图片保存时随机失败、文件损坏问题求助
你的问题同时存在async/await使用不当和服务端限流两方面原因,代码里的问题放大了服务端限流的影响,具体问题和修复方案如下:
代码存在的核心问题
1. 并发控制完全失效
你现在的分批逻辑是完全无效的:Task.Run() 调用的瞬间任务就已经开始执行,你先把所有请求对应的Task全部创建启动,之后再分批等待,等于程序启动瞬间所有请求就全部发向服务端,并发量根本不受3的限制,直接触发服务端限流规则,出现大量500、502、超时错误。
2. 异步逻辑中误用同步阻塞
GenerateFromSeed 方法中用 Thread.Sleep() 做延迟,会阻塞线程池线程,不仅浪费线程资源,还会打乱异步调度逻辑,导致请求超时概率升高,应该替换为 await Task.Delay()。
3. RestClient实例创建不当
每次请求都新建 RestClient 实例会导致套接字资源被快速耗尽,出现大量状态码0的超时错误,RestSharp官方推荐整个应用生命周期共用一个单例的RestClient实例。
4. Random线程不安全
Random 类本身不是线程安全的,你在多线程并发的 GenerateFromSeed 方法中调用 random.Next(),会损坏Random的内部状态,轻则返回异常值,重则抛出未捕获的异常,这也是 GenerateSeed 经常返回0的核心原因。
5. 缺少返回内容校验
你仅判断HTTP状态码为200就直接将返回内容存为jpg,当服务端返回的是500/502的错误页面但状态码异常时,会直接把HTML内容存为jpg文件,表现为文件损坏。
服务端侧的问题
libraryofbabel.info 本身使用Cloudflare做防护,对高频批量请求默认会做限流拦截,同时站点生成大seed对应的图片需要消耗大量服务器算力,请求频率过高时服务端本身也会处理失败返回500错误,这类问题无法完全避免,只能通过合理控制请求频率降低触发概率。
优化建议
- 重写并发控制逻辑:每批最多创建3个任务,等待这批任务全部执行完成后再创建下一批,严格控制并发量不超过3
- 替换所有
Thread.Sleep()为await Task.Delay() - 将RestClient改为全局单例
- 给Random的所有访问加锁,或者使用
ThreadLocal<Random>保证线程安全 - 新增返回内容校验:判断响应头的
Content-Type是否为image/jpeg,或者校验返回字节流的前三位是否为jpg文件头0xFF 0xD8 0xFF,确认是有效图片再写入本地 - 新增请求重试机制:遇到500、502、超时错误时,间隔3-5秒后重试,单请求最多重试3次,可大幅提升成功率
内容的提问来源于stack exchange,提问作者Merdaiolo

