ASP.NET Core循环调用Task.Delay(50)是否会引发内存泄漏?
结论
你当前的代码不存在内存泄漏,这个告警绝大多数情况下可以直接忽略,不需要特殊处理。
告警原因拆解
ReSharper DPA 报的TimerQueueTimer分配是Task.Delay的正常底层行为:
- 每次调用
Task.Delay,.NET 运行时都会在定时器队列新建一个TimerQueueTimer实例,等延迟时间到、关联的Task状态流转为完成后,这个实例就失去引用,等待GC回收 - 你设置的50ms执行间隔非常短,每秒会创建20个Timer实例,GC不是实时回收内存的,会等到小对象堆分配达到Gen0回收阈值才会批量清理这些短生命周期对象,所以你会看到几MB的当前分配、上百MB的峰值——这只是两次GC间隔内攒下的临时对象,不是泄漏,回收后内存会立刻回落。
提到的「Task.Delay创建快于完成导致泄漏」的场景和你当前代码无关
相关结论里的泄漏问题有严格的触发前提:代码没有await Task.Delay,在循环里无脑触发即忘(fire-and-forget),完全不等待前序Delay任务完成,比如下面这种错误写法才会出现Timer无限堆积:
// 错误写法:没有await,会无限创建Timer导致泄漏 while (!stoppingToken.IsCancellationRequested) { _ = Task.Delay(50, stoppingToken); // 不等待,直接丢出去跑 DoWork(); }
你现在的代码是await Task.Delay(...),属于顺序执行:上一轮的业务逻辑+Delay完全执行结束前,根本不会进入下一轮循环创建新的Timer,天然不可能出现创建速度快于完成速度的问题。
哪些情况需要你调整?
只有出现以下场景时才需要处理,否则不用管这个告警:
- 生产环境观测到进程内存持续线性上涨,连续运行数天没有回落,通过内存dump确认大量
TimerQueueTimer实例存在持续引用、没有被GC回收 - 你循环内的业务逻辑执行耗时长期接近甚至超过50ms,导致实际调度间隔达不到预期,任务出现堆积
- 你对临时分配非常敏感,想彻底消掉这个DPA告警
如果要优化,直接用.NET 6 内置的PeriodicTimer替换原来的Task.Delay循环即可,它内部会复用单个定时器实例,不会每轮产生新的TimerQueueTimer分配,性能更好:
public class DemoWorker : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(50)); try { while (await timer.WaitForNextTickAsync(stoppingToken)) { // 执行业务逻辑 Console.WriteLine(DateTimeOffset.Now); } } catch (OperationCanceledException) { // 服务停止触发取消,属于正常流程,忽略即可 } } }
内容的提问来源于stack exchange,提问作者regnauld
相关产品推荐
相关产品推荐

