如何在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
相关产品推荐
相关产品推荐

