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

.NET 6中用Polly重试请求时遇流已消耗错误求助

解决ASP.NET Core中Polly重试时的"Stream was already consumed"错误

错误原因分析

  1. 请求内容流无法重复读取:Post请求的HttpContent默认是单次消费流,首次发送后流指针会移到末尾,重试时无法重新读取内容。
  2. 同步阻塞读取响应流:在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 10:57:17