You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Timer报错:无法访问已释放的IServiceProvider对象原因排查

问题原因与解决方案

核心原因

  1. 服务生命周期不匹配
    如果你的EmailVerificationService被注册为Scoped或Transient类型,当它所在的作用域结束后,DI容器会回收该服务实例,导致注入的IServiceProvider被释放。但Timer基于线程池运行,会持续持有对服务实例的引用,第二次触发回调时就会访问已被释放的IServiceProvider,抛出异常。

  2. async void回调的风险
    CheckVerificationExpiration使用了async void,这种异步方法的异常无法被上层捕获,会直接导致进程崩溃。更关键的是,async void会让方法执行脱离当前实例的生命周期管理,即便实例已被回收,回调仍可能继续执行。

  3. 未实现资源清理
    你的服务没有实现IDisposable接口来停止Timer,导致服务实例被回收后,Timer依然在后台运行,持续尝试访问已释放的成员。

解决方案

1. 将服务注册为Singleton

因为服务需要长期运行Timer,必须保证实例不会被DI容器提前回收。在Program.cs中注册:

builder.Services.AddSingleton<EmailVerificationService>();

2. 实现IDisposable清理Timer

添加资源清理逻辑,确保服务被回收时停止Timer,避免无效回调。

3. 替换async void为async Task并捕获异常

修改回调方法为async Task,配合try/catch捕获异常,防止进程崩溃,同时确保异步操作正确完成。

修改后的完整代码

public class EmailVerificationService : IDisposable
{
    private Timer? _timer;
    private string? _email;
    private readonly IServiceProvider _serviceProvider;
    private bool _disposed = false;

    public EmailVerificationService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void Start(string email)
    {
        _email = email;
        // 用lambda包装async Task回调
        _timer = new Timer(async _ => await CheckVerificationExpiration(), null, TimeSpan.Zero, TimeSpan.FromSeconds(3));
    }

    private async Task CheckVerificationExpiration()
    {
        // 已释放或无邮箱时直接返回
        if (_disposed || string.IsNullOrEmpty(_email))
            return;

        try
        {
            var now = DateTime.UtcNow;

            using var scope = _serviceProvider.CreateScope();
            await using var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var user = await dbContext.Users
                .Where(u => u.Email == _email && now >= u.ConfirmCodeExpires)
                .FirstOrDefaultAsync();

            if (user != null)
            {
                dbContext.Users.Remove(user);
                await dbContext.SaveChangesAsync();
            }
        }
        catch (Exception ex)
        {
            // 此处添加日志记录,避免异常逃逸
            // 例如:_logger.LogError(ex, "检查邮箱验证过期状态时发生错误");
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            // 停止Timer并释放资源
            _timer?.Change(Timeout.Infinite, Timeout.Infinite);
            _timer?.Dispose();
        }

        _disposed = true;
    }
}

关键注意点

  • 根IServiceProvider(Singleton注入的)不会被DI容器释放,所以在回调中创建作用域是安全的。
  • 使用await using确保AppDbContext被正确释放。
  • 所有异步操作必须用await等待,避免异步操作未完成就结束方法。

内容的提问来源于stack exchange,提问作者Revan Zakaryali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 02:52:20