System.Threading.Timer在服务器负载时无法周期性执行问题求助
问题根源分析
1. Async void 回调的隐患
你使用的System.Threading.Timer回调属于TimerCallback委托,要求返回void。而你传入的async (timerState) => await UpdateActivityAsync(_ipAddress)本质是async void方法,存在两个致命问题:
- 异步操作抛出的异常会直接扩散到线程池,无法被常规
try/catch捕获,一旦异常发生,可能导致线程池线程终止,进而中断Timer的后续调度。 - 服务器负载较高时,异步操作可能长时间处于等待状态,Timer会误判回调已执行完成并继续触发下一次回调,当线程池资源被占满后,后续回调任务无法被调度,最终导致Timer看似“停止工作”。
2. HttpClient 管理不当(对应日志中的HttpMessageHandler清理)
日志里的HttpMessageHandler清理记录,说明UpdateActivityAsync方法中大概率频繁创建HttpClient实例。每次创建HttpClient都会生成一个HttpMessageHandler,这些Handler不会被立即释放,堆积到一定数量后GC会触发清理周期,该过程会占用大量CPU和内存资源,进一步加剧服务器负载,抢占Timer调度所需的线程池资源。
修复方案
方案1:修正Timer的异步回调写法
避免异常扩散到线程池,在async void回调内部捕获所有异常:
_activityTimer = new Timer(async (timerState) => { try { await UpdateActivityAsync(_ipAddress); } catch (Exception ex) { // 记录异常日志,防止异常影响Timer调度 Serilog.Log.Error(ex, "UpdateActivityAsync执行失败"); } }, null, new Random().Next(1, 15000), 15000);
方案2:正确管理HttpClient实例
不要在UpdateActivityAsync中频繁创建HttpClient,改用以下两种方式复用实例:
- 单例模式(非ASP.NET Core项目):
// 在类中定义静态HttpClient实例,全局复用 private static readonly HttpClient _httpClient = new HttpClient(); private async Task UpdateActivityAsync(string ipAddress) { var response = await _httpClient.GetAsync($"http://{ipAddress}/activity"); // 后续业务逻辑 }
- IHttpClientFactory(ASP.NET Core项目):
先在Program.cs中注册服务:
builder.Services.AddHttpClient();
再注入使用:
private readonly IHttpClientFactory _httpClientFactory; // 构造函数注入 public YourService(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } private async Task UpdateActivityAsync(string ipAddress) { var httpClient = _httpClientFactory.CreateClient(); var response = await httpClient.GetAsync($"http://{ipAddress}/activity"); // 后续业务逻辑 }
这种方式能避免HttpMessageHandler堆积,减少GC清理频率,降低服务器负载。
方案3:确保Timer的生命周期正确
如果Timer所在的类是短暂生命周期对象(比如请求作用域服务),类被GC回收时Timer会自动停止。需确保Timer所在类是单例或长期存在的实例,或手动保持对Timer的引用,防止被GC回收。
内容的提问来源于stack exchange,提问作者Dipak Shinde
相关产品推荐
相关产品推荐

