我正在使用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();
}
如果MyContext
有InstancePerLifetimeScope
那么当我尝试使用它时 - 我有一个错误: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();
}
});
}
}
但是我想知道如何重构这部分:
List<Type> jobTypes = new List<Type> { typeof(ExpiryDateTask) };
- 该列表应以某种方式由实现的所有类型的任务填充 IJob 界面。var job = scope.Resolve<IJob>();
我认为这是错误的,应该看起来更像var job = resolveJob(jobType)
- 所以基本上基于类型。
发生这种情况很可能是因为作业是异步运行的,也就是说job.Run()
在执行退出 using 语句后调用。在调用job.Run()
时,生存期范围已释放,包括基础数据库上下文。
尝试将 using 语句移动到调度程序回调中。毕竟,在单独的生存期上下文中执行每个计划作业是最有意义的。
更多详情:
我想MyContext
被注入到实现IJob
的类的构造函数中,对吧?关于您的原始代码,我认为有两个相互矛盾的目标:
- 您可以预先实例化每个作业以
GetTimeInterval
并对其进行计划
每个 - 作业可以运行多次,每个执行都应有自己的数据库上下文,并在完成后释放它。
不可能同时获得两者。你需要分开 执行部分中的计划部分。
一种方法是使用 Autofac 的Metadata
.它允许您在实际实例化组件之前考虑有关组件的上下文信息。我写了一个工作代码示例来演示如何做到这一点。
它归结为:
-
使用元数据注册作业
builder.RegisterType<JobImplementation>() .WithMetadata<JobMetadata>(conf => conf.For(meta => meta.Interval, TimeSpan.FromSeconds(30)) .For(meta => meta.Type, typeof(JobImplementation)) ) .As<IJob>() .InstancePerLifetimeScope();
-
使用此元数据解析调度程序回调中的特定作业实例
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());
});