使用Timer定时刷新缓存时触发Cannot access a disposed context异常
Entity Framework定时刷新缓存时上下文已释放的问题解决
错误原因
默认情况下,AddDbContext注册的PostgreSqlContext是Scoped生命周期——DI容器会为每个作用域创建一个实例,作用域结束后自动释放。而你的SomeLogic如果是Singleton(因为Timer长期运行,这类服务通常注册为Singleton),构造函数注入的IProductRepository会持有同一个Scoped的PostgreSqlContext实例。第一次调用BuildCache时上下文正常使用,之后DI容器释放了这个Scoped实例,Timer第二次触发时就会访问已被释放的上下文,抛出异常。
解决方案:每次刷新时创建新的作用域
不要在Singleton服务中直接持有Scoped服务的实例,而是注入IServiceProvider,每次需要访问仓储时创建新的作用域,获取全新的上下文实例:
修改SomeLogic代码:
public class SomeLogic { private readonly IServiceProvider _serviceProvider; private Timer _timer; public SomeLogic(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; BuildCache(); _timer = new Timer(ReloadHandler, null, 60000, Timeout.Infinite); } private void ReloadHandler(object state = null) { BuildCache(); _timer.Change(60000, Timeout.Infinite); } public void BuildCache() { using (var scope = _serviceProvider.CreateScope()) { var productRepository = scope.ServiceProvider.GetRequiredService<IProductRepository>(); var products = productRepository.GetAllProducts(); // 处理缓存逻辑 } } }
CreateScope()会创建一个新的DI作用域,作用域内的Scoped服务(包括PostgreSqlContext)会被重新实例化using语句会自动释放作用域,确保上下文被正确回收,避免资源泄漏
其他定时刷新缓存的实现方式
1. 使用.NET自带的BackgroundService(推荐)
BackgroundService是.NET官方提供的后台服务框架,比手动使用Timer更规范,自带生命周期管理和取消支持:
public class CacheRefreshService : BackgroundService { private readonly IServiceProvider _serviceProvider; private readonly ILogger<CacheRefreshService> _logger; public CacheRefreshService(IServiceProvider serviceProvider, ILogger<CacheRefreshService> logger) { _serviceProvider = serviceProvider; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // 首次执行 await RefreshCacheAsync(); while (!stoppingToken.IsCancellationRequested) { // 等待5分钟后再次执行 await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); await RefreshCacheAsync(); } } private async Task RefreshCacheAsync() { try { using (var scope = _serviceProvider.CreateScope()) { var productRepository = scope.ServiceProvider.GetRequiredService<IProductRepository>(); var products = productRepository.GetAllProducts(); // 处理缓存逻辑 } } catch (Exception ex) { _logger.LogError(ex, "缓存刷新任务执行失败"); } } }
在Startup中注册后台服务:
services.AddHostedService<CacheRefreshService>();
2. 使用第三方定时任务库(如Hangfire)
如果需要更复杂的定时规则(CRON表达式)、重试机制或可视化管理,可以使用Hangfire这类库。它支持多种存储(包括PostgreSQL),能轻松实现复杂的定时任务调度,适合业务场景复杂的系统。
内容的提问来源于stack exchange,提问作者Вася
相关产品推荐
相关产品推荐

