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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 01:40:28