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

使用HttpClient调用API认证接口无响应问题排查

问题分析与解决方案

你的代码在await _client.SendAsync(request)处挂起,结合API已返回响应但客户端无反馈的现象,核心问题大概率是异步同步混合调用导致的死锁,以下是具体排查和解决步骤:

1. 排查同步阻塞引发的死锁

如果调用GetAuthenticatedClientAsync或GetAuthenticationTokenAsync的代码使用了.Result或.Wait()(而非await),会触发经典异步死锁:

  • await会捕获当前线程的同步上下文(比如ASP.NET的SynchronizationContext)
  • .Result/.Wait()会阻塞当前线程等待异步完成,导致上下文被占用
  • 异步方法执行完成后无法回到原上下文,最终永久挂起

解决办法:

  • 全程使用异步调用链,所有调用这些方法的地方都用await,禁止使用.Result/.Wait()
  • 若无法修改调用链,在所有await后添加.ConfigureAwait(false),避免捕获同步上下文:
    // 修改SendAsync的await
    var httpResponseMessage = await _client.SendAsync(request).ConfigureAwait(false);
    // 其他await语句同理
    string jsonResponse = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
    var retry = await GetAuthenticationTokenAsync(true).ConfigureAwait(false);
    var token = await GetAuthenticationTokenAsync(false).ConfigureAwait(false);
    

2. 优化HttpClient的管理方式

你当前手动创建单例HttpClient,虽然可行,但在ASP.NET环境下更推荐用IHttpClientFactory管理,避免连接池耗尽、DNS缓存过期等潜在问题:

  • 注册服务:
    // 在Program.cs中
    builder.Services.AddHttpClient<AppointmentService>(client =>
    {
        client.BaseAddress = new Uri(ConfigService.WebApiUrl());
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    });
    
  • 修改AppointmentService的构造函数注入:
    private readonly HttpClient _client;
    private string _cachedToken;
    private DateTime _tokenExpiry;
    
    public AppointmentService(HttpClient client)
    {
        _client = client;
    }
    

3. 验证请求路径拼接正确性

检查ConfigService.WebApiUrl()返回的BaseAddress是否以斜杠结尾:

  • 如果BaseAddress是http://your-api.com,拼接相对路径api/v1/auth/token会得到错误的http://your-api.comapi/v1/auth/token
  • 确保BaseAddress末尾添加斜杠,比如http://your-api.com/,拼接后路径才正确

4. 重构重试逻辑避免递归风险

当前的递归重试逻辑如果遇到非成功响应,会递归调用自身,虽你说API返回200,但递归过程中若出现未捕获异常,也可能导致挂起。建议改为非递归实现:

private async Task<string> GetAuthenticationTokenAsync()
{
    int retryCount = 0;
    const int maxRetries = 1;

    while (retryCount <= maxRetries)
    {
        // 检查缓存逻辑
        if (!string.IsNullOrEmpty(_cachedToken) && DateTime.UtcNow < _tokenExpiry)
        {
            return _cachedToken;
        }

        try
        {
            var apiClientId = ConfigService.ApiClientId();
            var apiClientSecret = ConfigService.ApiClientSecret();

            var tokenRequest = new AuthTokenRequest
            {
                ClientId = apiClientId,
                ClientSecret = apiClientSecret,
                ApplicationName = "CallingApp"
            };

            var content = JsonConvert.SerializeObject(tokenRequest, Formatting.None);
            var json = new StringContent(content, Encoding.UTF8, "application/json");

            var request = new HttpRequestMessage()
            {
                Method = HttpMethod.Post,
                RequestUri = new Uri("api/v1/auth/token", UriKind.Relative),
                Content = json
            };

            var httpResponseMessage = await _client.SendAsync(request).ConfigureAwait(false);
            
            if (httpResponseMessage.IsSuccessStatusCode)
            {
                string jsonResponse = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
                var auth = JsonConvert.DeserializeObject<AuthTokenResponse>(jsonResponse);
                _cachedToken = auth.Token;
                _tokenExpiry = DateTime.UtcNow.AddMinutes(auth.ExpiresIn - (5 * 60));
                return _cachedToken;
            }
        }
        catch (Exception ex)
        {
            ErrorHandler.RaiseError(ex);
        }

        retryCount++;
        await Task.Delay(1000); // 添加重试延迟避免频繁请求
    }

    throw new InvalidOperationException("Failed to generate authentication token after retries");
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 13:43:17