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

Autofac结合NServiceBus调度器的DbContext释放与依赖注入优化问题

重构方案:自动扫描IJob实现 + 按类型解析实例

我来帮你搞定这两个重构点,让你的代码更灵活易维护,先回顾下你的问题背景:你最初在Start方法里创建了一个外层生命周期范围,解析所有IJob后调度执行,但因为调度委托是延迟触发的,当委托真正执行时,外层的scope已经被释放,导致依赖的MyContext被Disposed,触发报错。改成SingleInstance虽然解决了报错,但引入了内存泄漏风险。你的修复思路——把生命周期范围的创建移到调度委托内部——完全正确,现在只需要优化那两个点:

1. 自动扫描所有IJob实现类型

我们可以通过反射自动扫描程序集,找到所有实现IJob接口的非抽象类,不用手动维护任务列表。如果你的IJob实现都在当前程序集里,用以下代码即可;如果分布在其他程序集,只需扩展扫描的程序集范围:

// 扫描当前程序集所有实现IJob的非抽象类
var jobTypes = Assembly.GetExecutingAssembly()
    .GetTypes()
    .Where(t => typeof(IJob).IsAssignableFrom(t) 
                && !t.IsAbstract 
                && !t.IsInterface)
    .ToList();

如果需要扫描多个程序集:

// 示例:扫描当前程序集和指定的Job程序集
var assemblies = new[] 
{
    Assembly.GetExecutingAssembly(),
    Assembly.Load("Your.Jobs.Assembly.Name")
};

var jobTypes = assemblies.SelectMany(a => a.GetTypes())
    .Where(t => typeof(IJob).IsAssignableFrom(t) 
                && !t.IsAbstract 
                && !t.IsInterface)
    .ToList();

2. 根据Job类型解析对应的实例

首先要确保Autofac已经自动注册了所有IJob实现,这样才能按类型正确解析。先完善你的Autofac注册逻辑:

第一步:完善Autofac依赖注册

在注册MyContext的代码块中,添加自动注册所有IJob实现的逻辑:

private static void RegisterDependencies(ContainerBuilder builder)
{
    // 注册MyContext,保持InstancePerLifetimeScope避免内存泄漏
    builder.Register(c => new MyContext(GetConnectionString()))
           .InstancePerLifetimeScope();

    // 自动注册所有IJob实现,每个实例按生命周期范围创建
    builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
           .Where(t => typeof(IJob).IsAssignableFrom(t))
           .As<IJob>()
           .InstancePerLifetimeScope();
}

第二步:修改Start方法,按类型解析Job

现在在Start方法中,遍历自动扫描到的jobTypes,在调度委托内部创建独立的生命周期范围,并解析对应类型的Job实例:

public void Start()
{
    // 自动扫描所有IJob实现类型
    var jobTypes = Assembly.GetExecutingAssembly()
        .GetTypes()
        .Where(t => typeof(IJob).IsAssignableFrom(t) 
                    && !t.IsAbstract 
                    && !t.IsInterface)
        .ToList();

    foreach (var jobType in jobTypes)
    {
        // 获取当前Job的调度间隔(这里提供两种常见实现方式)
        var interval = GetJobInterval(jobType);

        _schedule.Every(interval, () =>
        {
            using (var scope = _lifetimeScope.BeginLifetimeScope())
            {
                // 解析对应类型的Job实例
                var job = (IJob)scope.Resolve(jobType);
                job.Run();
            }
        });
    }
}

// 方式1:通过自定义Attribute获取间隔(推荐,更优雅)
private TimeSpan GetJobInterval(Type jobType)
{
    var intervalAttr = jobType.GetCustomAttribute<JobIntervalAttribute>();
    return intervalAttr?.Interval ?? TimeSpan.FromSeconds(30);
}

// 自定义Attribute示例
[AttributeUsage(AttributeTargets.Class)]
public class JobIntervalAttribute : Attribute
{
    public TimeSpan Interval { get; }

    public JobIntervalAttribute(int seconds)
    {
        Interval = TimeSpan.FromSeconds(seconds);
    }
}

// 使用Attribute标记Job的间隔
[JobInterval(30)]
public class ExpiryDateTask : IJob
{
    // ... Job实现代码
}

关键注意点

  • 把BeginLifetimeScope放在调度委托内部,确保每次Job执行时都有独立的生命周期范围,MyContext会在scope结束时自动释放,既避免了DbContext被Disposed的错误,又不会导致内存泄漏。
  • 自动扫描+注册的方式让你新增IJob实现时,不需要手动修改Start方法,代码扩展性大幅提升。
  • 用自定义Attribute标记调度间隔,比通过Job实例获取间隔更高效,避免创建临时实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:24:51