Quartz.NET 3.0似乎在同一范围内启动所有作业 [英] Quartz.NET 3.0 seems to launch all jobs in the same scope

查看:115
本文介绍了Quartz.NET 3.0似乎在同一范围内启动所有作业的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在定义了两个依赖于范围服务(ScopedDataAccess)的作业之后,我很难将Quartz 3.0.7与ASP.NET Core 2.2结合使用:

I have a hard time using Quartz 3.0.7 with ASP.NET Core 2.2 after I have defined two jobs that rely on a scoped service (ScopedDataAccess) that is a wrapper upon my database context:

services.AddScoped<IScopedDataAccess, ScopedDataAccess>();

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

问题在于两个作业都接收到作用域服务的相同实例(并因此接收到相同的数据库上下文),从而由于并行使用而使上下文崩溃.

The issue is that both jobs receive the same instance of the scoped service (and thus the same database context), thus crashing the context due to parallel usage.

我的代码如下:

工作被定义为作用域",我期望每个实例都在其自己的作用域"中运行

Jobs are defined as "scoped" and my expectation is for each instance to run in its own "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为不同的作业获取不同的作用域?

Is there a way to use Quartz.NET to obtain different scopes for different jobs?

推荐答案

据我所知,这在Quartz中是不可能的,我在同样的问题上苦苦挣扎,发现的唯一解决方案是使用ServiceLocator并显式创建范围在工作中.

As i know, this is not possible with Quartz, i struggled with the same issues and the only solution i found was to use a ServiceLocator and create the scope explicitly in the 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();
        }
    }
}

在这种情况下,您的工作人员仍然处于范围内,但工作不再存在.因此,您仍然可以在解决方案中的其他位置使用工作者",并且范围仍然有效. 您需要根据您使用的DI自己实现ServiceLocator,并且IServiceLocator也必须由您定义.

In this case, your worker is still scoped but the job isn't anymore. So you can still use your Worker in other places in your solution and the scope still works. You need to implement the ServiceLocator by yourself depending on the DI do you use and IServiceLocator must also be defined by you.

修改

在我们的一个项目中,我们使用以下代码:

In one of our projects we use this:

/// <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:

We use mostly SimpleInjector with this implementation:

/// <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框架. 希望这对您了解所需的实现有所帮助.

As you can see, this is just a simple wrapper but helps to hide the real DI Framework from the consumers. I hope this helps a little bit to understand your needed implementation.

这篇关于Quartz.NET 3.0似乎在同一范围内启动所有作业的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆