未持有引用的Scoped服务是否会在作用域结束前被释放?
问题
我想要创建一个在后台运行、按固定间隔执行定时任务的服务TaskTimer。由于它需要在另一个HostedService完成启动后才开始运行,因此无法直接将其注册为HostedService,而是在该HostedService(BotService)内部创建作用域,将其作为Scoped Service获取并运行:
services.AddScoped<TaskTimer>(); services.AddHostedService<BotService>();
该定时服务的实现如下:
using Microsoft.Extensions.Logging; namespace MyBot; internal class TaskTimer : IDisposable { #region Private vars private readonly ILogger<TaskTimer> _log; #endregion #region Constructors public TaskTimer(ILogger<TaskTimer> log) { _log = log; _log.LogInformation("Starting task timer"); // [start timer here] } #endregion public void Dispose() { _log.LogInformation("Stopping task timer"); // [stop timer here] } }
随后在主BotService中:
public class BotService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // [do init here...] using (var botServiceScope = _serviceProvider.CreateScope()) { // Start task timer _ = botServiceScope.ServiceProvider.GetRequiredService<TaskTimer>(); // [await stoppingToken] } } }
目前测试运行正常,但我担心由于丢弃了GetRequiredService的返回值(未持有该服务实例的引用),TaskTimer可能会在using作用域结束前就被释放。请问即使不持有服务实例引用,是否能保证该服务仅在作用域关闭时才被释放?
回答
不能保证。当前测试正常只是偶然情况,这种写法存在隐患:
- 调用
GetRequiredService<TaskTimer>()时,DI容器会创建实例并在作用域内跟踪它,但容器对实例的引用通常不会阻止垃圾回收(GC)。如果你的代码没有持有该实例的强引用,GC随时可能回收它,导致Dispose被提前触发,定时任务意外终止。 - DI容器的跟踪只是为了在作用域结束时统一执行释放,但它不会主动维持实例的存活——只有代码中的强引用才能确保实例在预期周期内不被回收。
正确做法
必须在作用域内持有TaskTimer的强引用,避免实例被提前GC回收:
public class BotService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // [do init here...] using (var botServiceScope = _serviceProvider.CreateScope()) { // 持有强引用,防止GC回收 var taskTimer = botServiceScope.ServiceProvider.GetRequiredService<TaskTimer>(); // 等待停止信号,确保作用域与BotService生命周期绑定 await stoppingToken; } } }
这样做的优势:
- 强引用会阻止GC在作用域结束前回收
TaskTimer实例 - 当
using块结束(作用域关闭)时,DI容器会自动调用TaskTimer的Dispose方法,确保定时任务被正确停止
内容的提问来源于stack exchange,提问作者Jez
相关产品推荐
相关产品推荐

