异步执行双任务时DbContext抛出System.ObjectDisposedException异常
问题分析:DbContext重复使用导致ObjectDisposedException
问题描述
开发环境下构建邮件测试机制,邮件配置信息存储在SystemSettings数据库表中。该功能在Controller中可正常运行,但在Messenger.cs助手类调用SafeTestingSendEmail()方法时,抛出System.ObjectDisposedException异常,提示重复使用了同一个DbContext实例,调用_repoSystemSettings.GetSystemSettings(type)分别获取DEV_BCC_EMAIL和DEV_EMAIL对应的Value值时出错。
数据库表结构
| Id | Name | Value |
|---|---|---|
| 1 | DEV_BCC_EMAIL | "myBcc@gmail.com,myPersonal@gmail.com" |
| 2 | DEV_EMAIL | "myPersonal@gmail.com" |
相关代码
SystemSettingsRepo.cs
public class SystemSettingsRepo : ISystemSettingsRepo { readonly DbContext _context; public SystemSettingsRepo(DbContext context) { _context = context; } public async Task<string> GetSystemSettings(string type = "") { var settings = await _context.SystemSettings.FirstOrDefaultAsync(s => s.IsActive == true && s.Name == type); return settings.Value; } }
ISystemSettingsRepo.cs (接口)
public interface ISystemSettingsRepo { Task<string> GetSystemSettings(string type = ""); }
Messenger.cs
private readonly ISystemSettingsRepo _repoSystemSettings; public Messenger(ISystemSettingsRepo repoSystemSettings) { _repoSystemSettings = repoSystemSettings; } public async void SafeTestingSendEmail(string receiver, string subject, string body, string cc = "") { var env; var bcc = "defaultBcc@email.com"; if (env.Length > 0 && env != "production") { var devBcc = await GetSystemSettingValue("DEV_BCC_EMAIL"); var devEmail = await GetSystemSettingValue("DEV_EMAIL"); receiver = devEmail; bcc = devBcc; cc = bcc; subject += "Testing Email."; SendEmail(receiver, subject, body, bcc, cc); } else { // production logic } } public async Task<string> GetSystemSettingValue(string type) { await Task.Delay(2000); return await _repoSystemSettings.GetSystemSettings(type); }
已尝试的解决方法
- 尝试过
Task.Delay()和Task.Run(),均无效。 - 当前改用同步方式,通过List获取数据库记录解决问题,但放弃了异步实现,寻求更优方案。
问题根源与优化方案
问题根源
async void的误用:SafeTestingSendEmail使用async void签名,这种写法仅适用于事件处理程序,普通异步方法使用它会导致异步操作脱离调用上下文,可能造成DbContext在异步操作完成前被提前释放。- DbContext生命周期不匹配:如果
Messenger是单例注入,而DbContext是Scoped生命周期,当Scoped结束后DbContext会被释放,后续调用就会触发ObjectDisposedException。 - 未处理空值:
GetSystemSettings中如果FirstOrDefaultAsync返回null,直接访问settings.Value会抛出NullReferenceException。
优化方案
1. 修正异步方法签名
将async void改为async Task,确保异步操作能被正确等待,避免DbContext提前释放:
public async Task SafeTestingSendEmail(string receiver, string subject, string body, string cc = "") { // 原有逻辑保持不变 }
调用该方法时需使用await等待异步完成。
2. 正确管理DbContext生命周期
如果Messenger是单例,避免直接注入Scoped的DbContext,改用IDbContextFactory创建DbContext实例,确保每次调用都使用新的上下文:
public class SystemSettingsRepo : ISystemSettingsRepo { readonly IDbContextFactory<YourDbContext> _contextFactory; public SystemSettingsRepo(IDbContextFactory<YourDbContext> contextFactory) { _contextFactory = contextFactory; } public async Task<string> GetSystemSettings(string type = "") { using var context = _contextFactory.CreateDbContext(); var settings = await context.SystemSettings.FirstOrDefaultAsync(s => s.IsActive == true && s.Name == type); return settings?.Value ?? string.Empty; // 添加空值判断 } }
3. 批量获取配置减少数据库访问
一次性查询所需的多个配置,减少DbContext的使用次数,同时提升性能:
// 在ISystemSettingsRepo中添加批量查询方法 Task<Dictionary<string, string>> GetSystemSettingsList(IEnumerable<string> types); // SystemSettingsRepo中的实现 public async Task<Dictionary<string, string>> GetSystemSettingsList(IEnumerable<string> types) { using var context = _contextFactory.CreateDbContext(); var settings = await context.SystemSettings .Where(s => s.IsActive == true && types.Contains(s.Name)) .ToDictionaryAsync(s => s.Name, s => s.Value); return settings; } // Messenger中的调用 var settings = await _repoSystemSettings.GetSystemSettingsList(new[] { "DEV_BCC_EMAIL", "DEV_EMAIL" }); var devBcc = settings.TryGetValue("DEV_BCC_EMAIL", out var bccVal) ? bccVal : "defaultBcc@email.com"; var devEmail = settings.TryGetValue("DEV_EMAIL", out var emailVal) ? emailVal : receiver;
内容的提问来源于stack exchange,提问作者thestoneoflapiz
相关产品推荐
相关产品推荐

