BeginLifetimeScope / DbContext 的 Autofac 和内存泄漏已被释放 / C# asp.



我正在使用NServiceBus Scheduler,这就是为什么我被迫使用BeginLifetimeScope来避免内存泄漏。

方法:

public void Start()
{
using (var scope = _lifetimeScope.BeginLifetimeScope())
{
var jobs = scope.Resolve<IEnumerable<IJob>>();
foreach (var job in jobs)
{
_schedule.Every(job.GetTimeInterval(), () =>
{
job.Run();
});
}
}
}

和上下文的 Autofac 配置:

private static void RegisterContext(ContainerBuilder builder)
{
builder.Register(c => new MyContext(GetConnectionString())).InstancePerLifetimeScope();
}

如果MyContextInstancePerLifetimeScope那么当我尝试使用它时 - 我有一个错误:System.InvalidOperationException: 'The operation cannot be completed because the DbContext has been disposed.'

因此,如果我将其更改为SingleInstance()那么当然一切正常,但上下文没有处理,我可能会有内存泄漏......


@EDIT

我已经解决了问题,这是解决方案:

public void Start()
{
List<Type> jobTypes = new List<Type> { typeof(ExpiryDateTask) };
foreach (var jobType in jobTypes)
{
_schedule.Every(TimeSpan.FromSeconds(30), () =>
{
using (var scope = _lifetimeScope.BeginLifetimeScope())
{
var job = scope.Resolve<IJob>();
job.Run();
}
});
}
}

但是我想知道如何重构这部分:

  1. List<Type> jobTypes = new List<Type> { typeof(ExpiryDateTask) };- 该列表应以某种方式由实现的所有类型的任务填充 IJob 界面。
  2. var job = scope.Resolve<IJob>();我认为这是错误的,应该看起来更像var job = resolveJob(jobType)- 所以基本上基于类型。

发生这种情况很可能是因为作业是异步运行的,也就是说job.Run()在执行退出 using 语句后调用。在调用job.Run()时,生存期范围已释放,包括基础数据库上下文。

尝试将 using 语句移动到调度程序回调中。毕竟,在单独的生存期上下文中执行每个计划作业是最有意义的。

更多详情:

我想MyContext被注入到实现IJob的类的构造函数中,对吧?关于您的原始代码,我认为有两个相互矛盾的目标:

  1. 您可以预先实例化每个作业以GetTimeInterval并对其进行计划
  2. 每个
  3. 作业可以运行多次,每个执行都应有自己的数据库上下文,并在完成后释放它。

不可能同时获得两者。你需要分开 执行部分中的计划部分。

一种方法是使用 Autofac 的Metadata.它允许您在实际实例化组件之前考虑有关组件的上下文信息。我写了一个工作代码示例来演示如何做到这一点。

它归结为:

  1. 使用元数据注册作业

    builder.RegisterType<JobImplementation>()
    .WithMetadata<JobMetadata>(conf =>
    conf.For(meta => meta.Interval, TimeSpan.FromSeconds(30))
    .For(meta => meta.Type, typeof(JobImplementation))
    )
    .As<IJob>()
    .InstancePerLifetimeScope();
    
  2. 使用此元数据解析调度程序回调中的特定作业实例

    using (var scope = container.BeginLifetimeScope())
    {
    //Select this one specific job by its metadata.
    var specificJobInitializer = scope
    .Resolve<IEnumerable<Meta<Func<IJob>, JobMetadata>>>()
    .Single(jobInitializer => jobInitializer.Metadata.Type == jobDefinition.Type);
    IJob job = specificJobInitializer.Value();
    job.Run();
    }
    

我将扩展pbalaga的答案。 为了避免处置DbContext的问题,一种选择是使用某种工厂进行DbContext并在内部创建DbContext作业。 另一种选择是使用提供延迟作业解析的不同调度程序,我们将 Quartz.NET 与 MassTransit 和 Topsshelf 一起使用,并且可以选择提供自己的调度程序,如下所示:

ScheduleJobServiceConfiguratorExtensions.SchedulerFactory = () => container.Resolve<IScheduler>();

如果我没记错的话,工作在应该被解雇的那一刻就解决了:

serviceConfigurator.ScheduleQuartzJob(q =>
{
q.WithJob(JobBuilder.Create<EventsJob>()
.WithIdentity("Events processing", "Events")
.Build);
q.AddTrigger(() => TriggerBuilder.Create().StartAt(DateTimeOffset.Now.AddSeconds(30))
.WithSchedule(SimpleScheduleBuilder.RepeatMinutelyForever(1)).Build());
});

最新更新