Xamarin升级MAUI后Polly重试遇“无法访问已关闭流”问题求助
问题原因
核心问题出在HttpContent对象无法被重复使用:当HttpClient.PostAsync首次执行时,会读取StringContent内部的流并将其关闭。Polly重试时再次传入同一个content实例,就会触发"Cannot access a closed Stream"异常。
解决方案
方案1:通过委托复用内容生成逻辑(推荐)
修改HttpManager的方法参数,接收一个能生成HttpContent的委托,确保每次重试都会创建全新的内容实例:
修改HttpManager.cs
readonly IConnectivity _connectivity; readonly AsyncPolicyWrap _retryPolicy = Policy .Handle<TimeoutRejectedException>() .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1), (exception, timespan, retryAttempt, context) => { App.AppViewModel.RetryTextVisible = true; App.AppViewModel.RetryText = $"Attempt number {retryAttempt}..."; }) .WrapAsync(Policy.TimeoutAsync(11, TimeoutStrategy.Pessimistic)); HttpClient HttpClient; public HttpManager(IConnectivity connectivity) { _connectivity = connectivity; HttpClient = new HttpClient(); } // 调整参数为Func<HttpContent>,让调用方提供内容生成逻辑 public async Task<ServiceResponse<T>> PostAsync<T>(string endpoint, Func<HttpContent> contentFactory, bool shouldRetry = true) { // ... 原有其他代码 var response = await Post($"http://10.0.2.2:5122/{endpoint}", contentFactory, shouldRetry); // ... 原有其他代码 } async Task<HttpResponseMessage> Post(string url, Func<HttpContent> contentFactory, bool shouldRetry) { if (shouldRetry) { var response = await _retryPolicy.ExecuteAndCaptureAsync(async token => // 每次重试都通过委托创建新的HttpContent await HttpClient.PostAsync(url, contentFactory(), token), CancellationToken.None); // ... 原有其他代码 } // ... 原有其他代码 }
修改AuthService.cs
private readonly IHttpManager _httpManager; public AuthService(IHttpManager manager) { _httpManager = manager; } public async Task<ServiceResponse<string>> Login(LoginDTO model) { var json = JsonConvert.SerializeObject(model); // 传入创建StringContent的委托,而非直接传实例 var response = await _httpManager.PostAsync<string>("Auth/Login", () => new StringContent(json, Encoding.UTF8, "application/json")); // ... 原有其他代码 }
方案2:临时快速修复(不改动HttpManager接口)
如果不想调整HttpManager的接口,可以在AuthService中把内容创建逻辑嵌入到重试流程中,但这种方式扩展性较差,仅适合临时验证:
// AuthService.cs中修改Login方法 public async Task<ServiceResponse<string>> Login(LoginDTO model) { var json = JsonConvert.SerializeObject(model); // 每次请求都创建新的StringContent var response = await _httpManager.PostAsync<string>("Auth/Login", new StringContent(json, Encoding.UTF8, "application/json")); // ... 原有其他代码 }
注:此方法仅在HttpManager的重试逻辑内部能重新触发内容创建时生效,若重试逻辑复用了传入的content实例,仍会报错,因此更推荐方案1。
额外优化建议
- 避免手动创建
HttpClient实例,改用MAUI内置的IHttpClientFactory注入,它会自动管理客户端生命周期,防止套接字耗尽问题。 - 扩展重试策略的异常捕获范围,除
TimeoutRejectedException外,可添加HttpRequestException、HTTP 5xx状态码等场景,让重试逻辑更全面。
内容的提问来源于stack exchange,提问作者Adrian Radulescu
相关产品推荐
相关产品推荐

