C# API返回429请求过频错误时重试任务持续失败问题排查
问题原因解析
1. 重试复用Task对象是核心错误
Task对象一旦开始执行,其最终状态(成功/失败/取消)就是永久固定的,你在调用externalAPI.UploadAsync(fileRequest, fileStream)的时候,这个异步方法就已经开始执行,返回的Task已经绑定了第一次执行的所有结果,后续循环里反复调用GetAwaiter().GetResult(),永远只会读取第一次执行的结果,不会重新发起上传请求,自然每次都抛出相同的异常。这种复用Task的做法完全不符合重试逻辑的要求。
2. fileStream的位置确实也会引发后续问题
哪怕你解决了Task复用的问题,第一次上传执行时已经把流的读取位置推进到了末尾,第二次重试时如果直接用同一个流上传,API会从流的当前位置(末尾)开始读取,要么上传空内容,要么直接触发参数异常。就算你没有提前释放流,位置不对也会导致重试失败。
优化方案
第一步:修改重试方法的入参为任务工厂
将入参从Task<T>改成Func<Task<T>>,每次重试都重新调用委托生成新的Task,触发全新的上传逻辑:
public static T RunWithRetries<T>(Func<Task<T>> taskFactory, int retryCount = 3) { for (int i = 0; i < retryCount; i++) { try { // 每次调用工厂生成新的Task,执行全新的逻辑 return taskFactory().GetAwaiter().GetResult(); } catch (Exception ex) { // 只有最后一次重试失败才抛出异常 if (i == retryCount - 1) throw; // 此处加你的重试等待逻辑,比如429对应等待Retry-After头的时长 Thread.Sleep(1000 * (i + 1)); } } // 理论上不会走到这里,兼容语法 return default; }
第二步:调整调用逻辑,处理流位置
调用时不要直接执行UploadAsync,而是把它包在委托里,每次执行前重置流的位置:
// 先确保流支持Seek,不支持的话需要每次重试重新打开流 if (fileStream.CanSeek) { fileStream.Position = 0; } SyncHelper.RunWithRetries(() => { // 每次重试都重置流位置 if (fileStream.CanSeek) { fileStream.Position = 0; } else { // 如果流不支持Seek,这里要重新打开文件流再调用上传 // fileStream = File.OpenRead(你的文件路径); } return externalAPI.UploadAsync(fileRequest, fileStream); });
内容的提问来源于stack exchange,提问作者Gab
相关产品推荐
相关产品推荐

