Quartz.NET 3.0, кажется, запускает все задания в одной области - PullRequest
1 голос
/ 17 марта 2019

Мне трудно использовать Quartz 3.0.7 с ASP.NET Core 2.2 после того, как я определил два задания, которые зависят от службы с ограниченным доступом (ScopedDataAccess), которая является оболочкой для моего контекста базы данных:

services.AddScoped<IScopedDataAccess, ScopedDataAccess>();

services.AddDbContext<AggregatorContext>(opt => opt.UseSqlServer(configuration.GetConnectionString("Default")));

Проблема заключается в том, что оба задания получают один и тот же экземпляр области действия (и, следовательно, один и тот же контекст базы данных), что приводит к сбою контекста из-за параллельного использования.

Мой код выглядит следующим образом:

Startup.cs

Задания определены как «ограниченные», и я ожидаю, что каждый экземпляр будет работать в своей собственной «области»

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 для получения разных областей для разных заданий?

1 Ответ

1 голос
/ 18 марта 2019

Как я знаю, это невозможно с 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 Framework от потребителей. Я надеюсь, что это поможет немного понять вашу необходимую реализацию.

...