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

如何在Polly重试策略中克隆HttpRequestMessage以避免复用报错?

解决Polly重试时复用HttpRequestMessage导致的异常问题

这个问题我做Xamarin项目时也踩过坑!确实HttpRequestMessage一旦被HttpClient.SendAsync调用后,内部内容流会被消耗,对象状态也会标记为已完成,重复使用必然抛出异常。要在Polly重试逻辑里解决这个问题,核心就是每次重试都生成全新的请求实例,而非复用同一个对象。

核心思路

别直接把创建好的HttpRequestMessage实例传给Polly,而是封装一个能每次调用都生成新请求的委托。这样Polly每触发一次重试,都会执行这个委托拿到新请求,从根本上避免复用问题。

步骤1:实现请求克隆/重新创建逻辑

如果已有配置好的原始请求(比如设置了大量请求头、内容),可以写扩展方法克隆它;如果请求逻辑简单,也可以每次直接创建新请求。

克隆请求的扩展方法

public static class HttpRequestMessageExtensions
{
    // 克隆整个HttpRequestMessage
    public static HttpRequestMessage Clone(this HttpRequestMessage request)
    {
        var clone = new HttpRequestMessage(request.Method, request.RequestUri)
        {
            Content = request.Content?.CloneContent(),
            Version = request.Version
        };

        // 克隆请求头
        foreach (var header in request.Headers)
        {
            clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
        }

        // 克隆内容头(如果有内容)
        if (request.Content != null)
        {
            foreach (var header in request.Content.Headers)
            {
                clone.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
            }
        }

        return clone;
    }

    // 克隆HttpContent(内容流只能被读取一次,需要复制到内存流复用)
    private static HttpContent CloneContent(this HttpContent content)
    {
        if (content == null) return null;

        var memoryStream = new MemoryStream();
        content.CopyToAsync(memoryStream).Wait();
        memoryStream.Position = 0;

        var clonedContent = new StreamContent(memoryStream);
        foreach (var header in content.Headers)
        {
            clonedContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
        }

        return clonedContent;
    }
}

直接创建新请求的委托

如果请求逻辑不复杂,也可以直接写一个委托,每次调用生成全新请求,还能动态带上最新的授权凭证:

Func<HttpRequestMessage> CreateNewRequest = () =>
{
    var request = new HttpRequestMessage(HttpMethod.Post, "some_endpoint")
    {
        Content = new StringContent("some content")
    };
    request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
    
    // 动态添加重新认证后的最新token
    if (!string.IsNullOrEmpty(CoreService.CurrentAuthToken))
    {
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", CoreService.CurrentAuthToken);
    }
    return request;
};

步骤2:修改Polly调用逻辑

把原来直接传入请求实例的代码,改成传入异步委托,每次重试时生成新请求并发送:

方式1:使用克隆的请求

// 先创建原始请求
var originalRequest = new HttpRequestMessage(HttpMethod.Post, "some_endpoint")
{
    Content = new StringContent("some content")
};
originalRequest.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

// 调用Polly策略,每次重试都克隆请求
var policyResponse = await ConnectivityHelper.RetryPolicy
    .ExecuteAndCaptureAsync(async () =>
    {
        using var clonedRequest = originalRequest.Clone();
        return await _client.SendAsync(clonedRequest).ConfigureAwait(false);
    }).ConfigureAwait(false);

方式2:使用每次新建请求的委托

var policyResponse = await ConnectivityHelper.RetryPolicy
    .ExecuteAndCaptureAsync(async () =>
    {
        using var newRequest = CreateNewRequest();
        return await _client.SendAsync(newRequest).ConfigureAwait(false);
    }).ConfigureAwait(false);

额外优化:修复重新认证的异步逻辑

看你提供的ReAuthPolicy代码,有个小问题:LogInWithSavedCredsAsync是异步方法,但你没加await等待完成,可能导致重新认证还没结束就发起重试,新凭证还没生效。修改如下:

private static IAsyncPolicy<HttpResponseMessage> ReAuthPolicy { get => 
    Policy.HandleResult<HttpResponseMessage>(x => x.StatusCode == HttpStatusCode.Unauthorized)
          .RetryAsync(async (_, __) => 
          {
              await CoreService.LogInWithSavedCredsAsync(true).ConfigureAwait(false);
          }); 
}

关键注意点

  • 一定要用using管理HttpRequestMessage的生命周期,它实现了IDisposable,避免内存泄漏。
  • 涉及重新认证时,确保新请求能获取到最新凭证(比如在创建/克隆请求时动态添加授权头)。
  • 永远不要直接复用已经被发送过的HttpRequestMessage实例。

内容的提问来源于stack exchange,提问作者vaindil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:18:41