Quartz.NET 3.0 似乎在同一范围内启动所有作业
Quartz.NET 3.0 seems to launch all jobs in the same scope
在我定义了两个依赖范围服务 (ScopedDataAccess) 的作业后,我很难将 Quartz 3.0.7 与 ASP.NET Core 2.2 一起使用,它是我的数据库上下文的包装器:
services.AddScoped<IScopedDataAccess, ScopedDataAccess>();
services.AddDbContext<AggregatorContext>(opt => opt.UseSqlServer(configuration.GetConnectionString("Default")));
问题是两个作业都接收同一个作用域服务实例(因此接收相同的数据库上下文),因此由于并行使用而导致上下文崩溃。
我的代码如下:
Startup.cs
作业定义为 "scoped",我的期望是每个实例在其自己的 "scope"
中 运行
private void ConfigureQuartz(IServiceCollection services, params Type[] jobs)
{
services.AddSingleton<IJobFactory, QuartzJobFactory>();
services.Add(jobs.Select(jobType => new ServiceDescriptor(jobType, jobType, ServiceLifetime.Scoped)));
services.AddSingleton(provider =>
{
var schedulerFactory = new StdSchedulerFactory();
var scheduler = schedulerFactory.GetScheduler().Result;
scheduler.JobFactory = provider.GetService<IJobFactory>();
scheduler.Start();
return scheduler;
});
}
protected void StartJobs(IApplicationBuilder app, IApplicationLifetime lifetime)
{
var scheduler = app.ApplicationServices.GetService<IScheduler>();
var configService = app.ApplicationServices.GetService<IConfigurationService>();
QuartzServicesUtilities.StartJob<ArticleXUserDataRefresherJob>(scheduler,
TimeSpan.FromSeconds(configService.ArticleXUserDataRefresherJobPeriod));
QuartzServicesUtilities.StartJob<LinkDataFetchJob>(scheduler,
TimeSpan.FromSeconds(configService.LinkDataJobPeriod));
lifetime.ApplicationStarted.Register(() => scheduler.Start());
lifetime.ApplicationStopping.Register(() => scheduler.Shutdown());
}
QuartzServicesUtilities
public class QuartzServicesUtilities
{
public static void StartJob<TJob>(IScheduler scheduler, TimeSpan runInterval)
where TJob : IJob
{
var jobName = typeof(TJob).FullName;
var job = JobBuilder.Create<TJob>()
.WithIdentity(jobName)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity($"{jobName}.trigger")
.StartNow()
.WithSimpleSchedule(scheduleBuilder =>
scheduleBuilder
.WithInterval(runInterval)
.RepeatForever())
.Build();
scheduler.ScheduleJob(job, trigger);
}
}
QuartzJobFactory
public class QuartzJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
public QuartzJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
var jobDetail = bundle.JobDetail;
var job = (IJob)_serviceProvider.GetService(jobDetail.JobType);
return job;
}
public void ReturnJob(IJob job) { }
}
有没有办法使用Quartz.NET为不同的工作获取不同的作用域?
据我所知,这在 Quartz 中是不可能的,我遇到了同样的问题,我找到的唯一解决方案是使用 ServiceLocator 并在作业中显式创建范围。
我以类似的方式结束:
// Pseudo-Code
public class MyJob : IJob
{
private readonly IServiceLocator _serviceLocator;
public MyJob(IServiceLocator serviceLocator)
{
_serviceLocator = serviceLocator;
}
public async Task Execute(JobExecutionContext context)
{
using(_serviceLocator.BeginScope())
{
var worker = _serviceLocator.GetService<MyWorker>();
await worker.DoWorkAsync();
}
}
}
在这种情况下,您的工作人员仍在范围内,但工作不再存在。因此,您仍然可以在解决方案的其他地方使用您的 Worker,并且作用域仍然有效。
ServiceLocator需要根据你使用的DI自己实现,IServiceLocator
也必须自己定义。
编辑
在我们的一个项目中,我们使用了这个:
/// <summary>
/// A simple service locator to hide the real IOC Container.
/// Lowers the anti-pattern of service locators a bit.
/// </summary>
public interface IServiceLocator
{
/// <summary>
/// Begins an new async scope.
/// The scope should be disposed explicitly.
/// </summary>
/// <returns></returns>
IDisposable BeginAsyncScope();
/// <summary>
/// Gets an instance of the given <typeparamref name="TService" />.
/// </summary>
/// <typeparam name="TService">Type of the requested service.</typeparam>
/// <returns>The requested service instance.</returns>
TService GetInstance<TService>() where TService : class;
}
我们在这个实现中主要使用 SimpleInjector:
/// <summary>
/// SimpleInjector implementation of the service locator.
/// </summary>
public class ServiceLocator : IServiceLocator
{
#region member vars
/// <summary>
/// The SimpleInjector container.
/// </summary>
private readonly Container _container;
#endregion
#region constructors and destructors
public ServiceLocator(Container container)
{
_container = container;
}
#endregion
#region explicit interfaces
/// <inheritdoc />
public IDisposable BeginAsyncScope()
{
return AsyncScopedLifestyle.BeginScope(_container);
}
/// <inheritdoc />
public TService GetInstance<TService>()
where TService : class
{
return _container.GetInstance<TService>();
}
}
如您所见,这只是一个简单的包装器,但有助于向消费者隐藏真正的 DI 框架。
我希望这有助于理解您需要的实施。
在我定义了两个依赖范围服务 (ScopedDataAccess) 的作业后,我很难将 Quartz 3.0.7 与 ASP.NET Core 2.2 一起使用,它是我的数据库上下文的包装器:
services.AddScoped<IScopedDataAccess, ScopedDataAccess>();
services.AddDbContext<AggregatorContext>(opt => opt.UseSqlServer(configuration.GetConnectionString("Default")));
问题是两个作业都接收同一个作用域服务实例(因此接收相同的数据库上下文),因此由于并行使用而导致上下文崩溃。
我的代码如下:
Startup.cs
作业定义为 "scoped",我的期望是每个实例在其自己的 "scope"
中 运行private void ConfigureQuartz(IServiceCollection services, params Type[] jobs)
{
services.AddSingleton<IJobFactory, QuartzJobFactory>();
services.Add(jobs.Select(jobType => new ServiceDescriptor(jobType, jobType, ServiceLifetime.Scoped)));
services.AddSingleton(provider =>
{
var schedulerFactory = new StdSchedulerFactory();
var scheduler = schedulerFactory.GetScheduler().Result;
scheduler.JobFactory = provider.GetService<IJobFactory>();
scheduler.Start();
return scheduler;
});
}
protected void StartJobs(IApplicationBuilder app, IApplicationLifetime lifetime)
{
var scheduler = app.ApplicationServices.GetService<IScheduler>();
var configService = app.ApplicationServices.GetService<IConfigurationService>();
QuartzServicesUtilities.StartJob<ArticleXUserDataRefresherJob>(scheduler,
TimeSpan.FromSeconds(configService.ArticleXUserDataRefresherJobPeriod));
QuartzServicesUtilities.StartJob<LinkDataFetchJob>(scheduler,
TimeSpan.FromSeconds(configService.LinkDataJobPeriod));
lifetime.ApplicationStarted.Register(() => scheduler.Start());
lifetime.ApplicationStopping.Register(() => scheduler.Shutdown());
}
QuartzServicesUtilities
public class QuartzServicesUtilities
{
public static void StartJob<TJob>(IScheduler scheduler, TimeSpan runInterval)
where TJob : IJob
{
var jobName = typeof(TJob).FullName;
var job = JobBuilder.Create<TJob>()
.WithIdentity(jobName)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity($"{jobName}.trigger")
.StartNow()
.WithSimpleSchedule(scheduleBuilder =>
scheduleBuilder
.WithInterval(runInterval)
.RepeatForever())
.Build();
scheduler.ScheduleJob(job, trigger);
}
}
QuartzJobFactory
public class QuartzJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
public QuartzJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
var jobDetail = bundle.JobDetail;
var job = (IJob)_serviceProvider.GetService(jobDetail.JobType);
return job;
}
public void ReturnJob(IJob job) { }
}
有没有办法使用Quartz.NET为不同的工作获取不同的作用域?
据我所知,这在 Quartz 中是不可能的,我遇到了同样的问题,我找到的唯一解决方案是使用 ServiceLocator 并在作业中显式创建范围。
我以类似的方式结束:
// Pseudo-Code
public class MyJob : IJob
{
private readonly IServiceLocator _serviceLocator;
public MyJob(IServiceLocator serviceLocator)
{
_serviceLocator = serviceLocator;
}
public async Task Execute(JobExecutionContext context)
{
using(_serviceLocator.BeginScope())
{
var worker = _serviceLocator.GetService<MyWorker>();
await worker.DoWorkAsync();
}
}
}
在这种情况下,您的工作人员仍在范围内,但工作不再存在。因此,您仍然可以在解决方案的其他地方使用您的 Worker,并且作用域仍然有效。
ServiceLocator需要根据你使用的DI自己实现,IServiceLocator
也必须自己定义。
编辑
在我们的一个项目中,我们使用了这个:
/// <summary>
/// A simple service locator to hide the real IOC Container.
/// Lowers the anti-pattern of service locators a bit.
/// </summary>
public interface IServiceLocator
{
/// <summary>
/// Begins an new async scope.
/// The scope should be disposed explicitly.
/// </summary>
/// <returns></returns>
IDisposable BeginAsyncScope();
/// <summary>
/// Gets an instance of the given <typeparamref name="TService" />.
/// </summary>
/// <typeparam name="TService">Type of the requested service.</typeparam>
/// <returns>The requested service instance.</returns>
TService GetInstance<TService>() where TService : class;
}
我们在这个实现中主要使用 SimpleInjector:
/// <summary>
/// SimpleInjector implementation of the service locator.
/// </summary>
public class ServiceLocator : IServiceLocator
{
#region member vars
/// <summary>
/// The SimpleInjector container.
/// </summary>
private readonly Container _container;
#endregion
#region constructors and destructors
public ServiceLocator(Container container)
{
_container = container;
}
#endregion
#region explicit interfaces
/// <inheritdoc />
public IDisposable BeginAsyncScope()
{
return AsyncScopedLifestyle.BeginScope(_container);
}
/// <inheritdoc />
public TService GetInstance<TService>()
where TService : class
{
return _container.GetInstance<TService>();
}
}
如您所见,这只是一个简单的包装器,但有助于向消费者隐藏真正的 DI 框架。 我希望这有助于理解您需要的实施。