在我定义了两个依赖于作用于我的数据库上下文的作用域的作用域服务(ScopedDataAccess)的作业之后,我很难使用Quartz 3.0.7和ASP.NET Core 2.2:
services.AddScoped<IScopedDataAccess, ScopedDataAccess>();
services.AddDbContext<AggregatorContext>(opt => opt.UseSqlServer(configuration.GetConnectionString("Default")));
问题是两个作业都接收到作用域服务的相同实例(因此也是相同的数据库上下文),从而因并行使用而导致上下文崩溃。
我的代码如下:
作业被定义为“作用域”,我期望每个实例都在自己的“范围”中运行
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());
}
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);
}
}
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并在Job中明确创建范围。
我结束了这样的事情:
// 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 Framework。我希望这有助于理解您所需的实施。