.NET 6中用Polly重试请求时遇流已消耗错误求助
解决ASP.NET Core中Polly重试时的"Stream was already consumed"错误
错误原因分析
- 请求内容流无法重复读取:Post请求的
HttpContent默认是单次消费流,首次发送后流指针会移到末尾,重试时无法重新读取内容。 - 同步阻塞读取响应流:在Polly的
OrResult同步委托中使用GetAwaiter().GetResult()强制同步读取响应JSON,不仅可能引发死锁,还会提前耗尽响应流,导致后续操作无法读取。
解决方案
1. 确保请求内容可重复读取
修改CreateRequest方法,使用ByteArrayContent替代StringContent,因为ByteArrayContent基于字节数组,支持多次读取:
private static HttpRequestMessage CreateRequest(string jsonContent, HttpMethod method, Uri uri) { var request = new HttpRequestMessage(method, uri); // 将JSON字符串转为字节数组,创建可重复读取的Content var contentBytes = Encoding.UTF8.GetBytes(jsonContent); var content = new ByteArrayContent(contentBytes); content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); request.Content = content; return request; }
2. 使用异步策略判断替代同步读取
将Polly策略中的OrResult改为OrResultAsync,异步读取响应内容,避免阻塞和流耗尽:
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy() { return HttpPolicyExtensions.HandleTransientHttpError() .OrResult(result => !result.IsSuccessStatusCode) // 改用异步判断逻辑 .OrResultAsync(async result => { try { var elem = await result.Content.ReadFromJsonAsync<JsonElement>(); return elem.TryGetProperty("error", out _); } catch { // 解析失败时不触发重试 return false; } }) .WaitAndRetryAsync( sleepDurations: Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(1), retryCount: 3), onRetryAsync: (outcome, timespan, retryCount, context) => { context.GetLogger()?.LogWarning("Delaying for {Delay}ms, then making retry {Retry}.", timespan.TotalMilliseconds, retryCount); return Task.CompletedTask; }); }
3. 额外保障:重试时重新克隆请求内容
如果无法修改CreateRequest方法,可在onRetryAsync中重新读取并克隆请求内容,确保重试时内容可用:
onRetryAsync: async (outcome, timespan, retryCount, context) => { context.GetLogger()?.LogWarning("Delaying for {Delay}ms, then making retry {Retry}.", timespan.TotalMilliseconds, retryCount); var originalRequest = outcome.Request; if (originalRequest.Content != null) { // 读取原始内容字节并重新创建Content var contentBytes = await originalRequest.Content.ReadAsByteArrayAsync(); originalRequest.Content = new ByteArrayContent(contentBytes); // 复制原始Content的头信息 originalRequest.Content.Headers.ContentType = originalRequest.Content.Headers.ContentType; } }
关键注意事项
- 避免在同步委托中调用异步方法并强制同步等待(如
GetAwaiter().GetResult()),这是ASP.NET Core中常见的死锁和流耗尽诱因。 - 对于需要重试的Post/Put请求,务必确保请求内容是可重复读取的类型(如
ByteArrayContent),而非单次消费流。
内容的提问来源于stack exchange,提问作者Snake Eyes
相关产品推荐
相关产品推荐

