Autofac and memory leak with BeginLifetimeScope / DbContext has been disposed / C# asp.net

丶灬走出姿态 提交于 2019-12-10 21:04:35

问题


I am using NServiceBus Scheduler that's why I'm forced to use BeginLifetimeScope to avoid memory leak.

Method:

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();
            });
        }
    }
}

And Autofac Configuration for context:

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

If MyContext has InstancePerLifetimeScope then whilst I'm trying to use it - I have an error: System.InvalidOperationException: 'The operation cannot be completed because the DbContext has been disposed.'

So if I'll change it to SingleInstance() then of course everything works but context isn't disposed and I can have memory leak...


@EDIT

I have fixed the problem, here is solution:

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();
            }
        });
    }
}

But I wondering how can I refactor this part:

  1. List<Type> jobTypes = new List<Type> { typeof(ExpiryDateTask) }; - that list should be filled somehow by all Types of Tasks that implement IJob interface.
  2. var job = scope.Resolve<IJob>(); I think this is wrong and should looks more like var job = resolveJob(jobType) - so basically based on the type.

回答1:


It happens most likely because the job is run asynchronously, that is job.Run() is called after execution exits the using statement. At the time job.Run() is called, the lifetime scope is already disposed including underlying database context.

Try moving the using statement inside of the scheduler callback. After all it makes most sense to execute each scheduled job in a separate lifetime context.

More details:

I suppose MyContext is injected into a constructor of a class that implements IJob, right? With respect to your original code I think there are two contradicting goals:

  1. you instantiate each job upfront to GetTimeInterval and schedule it
  2. each job can be run multiple times and each execution should have it own DB context and dispose of it when it's done.

It's not possible to get both at the same time. You need to separate the scheduling part from the execution part.

One way to do it is to use Autofac's Metadata. It allows you to take contextual info about a component into account before actually instantiating it. I've written up a working code sample that shows how to do it.

It boils down to:

  1. registering a job with metadata

    builder.RegisterType<JobImplementation>()
        .WithMetadata<JobMetadata>(conf =>
            conf.For(meta => meta.Interval, TimeSpan.FromSeconds(30))
                .For(meta => meta.Type, typeof(JobImplementation))
                )
        .As<IJob>()
        .InstancePerLifetimeScope();
    
  2. resolving the specific job instance in scheduler callback using this metadata

            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();
            }
    



回答2:


I will extend on pbalaga answer. To avoid problem with disposed DbContext, one option is to use some kind of factory for DbContext and create DbContext inside job. Another option is to use different scheduler that offers delayed job resolution, we used Quartz.NET with MassTransit and Topshelf, and there was option to provide own Scheduler like this:

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

and if I recall correctly, job was resolved at the moment when it was supposed to be fired:

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());
            });


来源:https://stackoverflow.com/questions/50611670/autofac-and-memory-leak-with-beginlifetimescope-dbcontext-has-been-disposed

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!