You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

动态设置IHttpClientFactory的超时时间

动态设置IHttpClientFactory的超时时间

老哥,我完全懂你的痛点——每次请求都手动套个CancellationTokenSource确实有点啰嗦,而且要统一改超时的话还得翻遍所有调用的地方,完全不符合你想要「全局动态调整、不用重启应用」的需求。我来给你分享几个更优雅的解决方案:

方案1:用自定义消息处理器(DelegatingHandler)集中处理动态超时

这是最推荐的方式,把超时逻辑抽离到全局的消息处理器里,所有通过这个HttpClient发出的请求都会自动应用最新的超时值,不用在每个请求里重复写冗余代码。

步骤1:创建线程安全的全局超时配置类

先整个单例类存当前的超时值,确保多线程环境下修改也不会出问题,这样我们可以在运行时随时调整它:

public class DynamicTimeoutConfig
{
    private TimeSpan _currentTimeout = TimeSpan.FromSeconds(60);
    private readonly object _lockObj = new object();

    public TimeSpan CurrentTimeout
    {
        get
        {
            lock (_lockObj)
            {
                return _currentTimeout;
            }
        }
        set
        {
            lock (_lockObj)
            {
                _currentTimeout = value;
            }
        }
    }
}

记得把这个类注册为单例服务:

services.AddSingleton<DynamicTimeoutConfig>();

步骤2:实现自定义超时消息处理器

继承DelegatingHandler,在发送请求前动态生成超时token,还要注意和请求本身的取消信号合并(别覆盖业务代码自己传的token):

public class DynamicTimeoutHandler : DelegatingHandler
{
    private readonly DynamicTimeoutConfig _timeoutConfig;

    public DynamicTimeoutHandler(DynamicTimeoutConfig timeoutConfig)
    {
        _timeoutConfig = timeoutConfig;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // 合并全局超时token与请求自带的取消token,避免冲突
        using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        timeoutCts.CancelAfter(_timeoutConfig.CurrentTimeout);

        try
        {
            return await base.SendAsync(request, timeoutCts.Token);
        }
        catch (OperationCanceledException ex)
        {
            // 区分是全局超时导致的取消,还是业务代码主动触发的取消
            if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
            {
                throw new TaskCanceledException("请求已超时", ex, _timeoutConfig.CurrentTimeout);
            }
            throw;
        }
    }
}

步骤3:注册HttpClient时绑定这个处理器

services.AddHttpClient<IMyBusinessService, MyBusinessService>()
        .AddHttpMessageHandler<DynamicTimeoutHandler>();

步骤4:动态修改超时值

在任何需要的地方注入DynamicTimeoutConfig,直接更新属性值就行,完全不用重启应用:

public class TimeoutManageController : ControllerBase
{
    private readonly DynamicTimeoutConfig _timeoutConfig;

    public TimeoutManageController(DynamicTimeoutConfig timeoutConfig)
    {
        _timeoutConfig = timeoutConfig;
    }

    [HttpPost("update-global-timeout")]
    public IActionResult UpdateTimeout(int seconds)
    {
        if (seconds <= 0)
        {
            return BadRequest("超时时间必须大于0");
        }
        _timeoutConfig.CurrentTimeout = TimeSpan.FromSeconds(seconds);
        return Ok($"全局请求超时已更新为{seconds}秒");
    }
}

方案2:结合动态配置刷新(.NET 6+)

如果你的项目用了配置中心(比如App Configuration、Consul)或者支持动态刷新的IConfiguration,可以直接把超时值存在配置里,让配置自动同步,Handler会自动读取最新值:

步骤1:配置文件中添加超时项

{
  "HttpClientGlobalSettings": {
    "DefaultTimeoutSeconds": 60
  }
}

步骤2:注册配置并开启动态刷新

public class HttpClientGlobalSettings
{
    public int DefaultTimeoutSeconds { get; set; } = 60;
}

// 注册配置并绑定
services.Configure<HttpClientGlobalSettings>(configuration.GetSection("HttpClientGlobalSettings"));
// 开启配置动态刷新(如果用App Configuration,需额外配置RefreshOptions)
services.AddOptions<HttpClientGlobalSettings>().Configure<IConfiguration>((settings, config) =>
{
    config.GetSection("HttpClientGlobalSettings").Bind(settings);
});

步骤3:修改消息处理器读取配置值

public class ConfigDrivenTimeoutHandler : DelegatingHandler
{
    private readonly IOptionsMonitor<HttpClientGlobalSettings> _settingsMonitor;

    public ConfigDrivenTimeoutHandler(IOptionsMonitor<HttpClientGlobalSettings> settingsMonitor)
    {
        _settingsMonitor = settingsMonitor;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var currentTimeout = TimeSpan.FromSeconds(_settingsMonitor.CurrentValue.DefaultTimeoutSeconds);
        using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        timeoutCts.CancelAfter(currentTimeout);

        try
        {
            return await base.SendAsync(request, timeoutCts.Token);
        }
        catch (OperationCanceledException ex)
        {
            if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
            {
                throw new TaskCanceledException("请求已超时", ex, currentTimeout);
            }
            throw;
        }
    }
}

这样只要修改配置(不管是本地配置文件还是配置中心的远程配置),配置值会自动刷新,所有后续请求都会立即应用新的超时时间。

为什么你原来的方式不够优雅?

你之前用using (var cts = new CancellationTokenSource(timeout))的方式本身是有效的,但它属于分散式处理——每个请求都要手动写这段逻辑,万一哪天要调整规则或者统一修改,你得找遍所有调用的地方,维护成本太高。而上面的方案是集中式处理,所有逻辑都收拢在Handler里,改一次就能全局生效,完美匹配你「不用重启、全局动态调整」的需求。

内容来源于stack exchange

火山引擎 最新活动