在 Asp.NET core 中触发 Quartz 作业时释放上下文

Posted

技术标签:

【中文标题】在 Asp.NET core 中触发 Quartz 作业时释放上下文【英文标题】:Context is disposed when triggering Quartz job in Asp.NET core 【发布时间】:2017-11-01 00:19:35 【问题描述】:

我正在尝试从 .NET 核心中的 Web API 控制器构建触发器 Quartz 作业。 我已经成功地通过 Startup 类的简单时间表创建了一个触发器:

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider services)
        
            loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseCors("CorsPolicy");

            app.UseApplicationInsightsRequestTelemetry();
            app.UseApplicationInsightsExceptionTelemetry();

            app.UseMvc();

            ProcessTracksScheduler.Start(services);          
        

这里是启动方法:

 public static class ProcessTracksScheduler
    
        public static void Start(IServiceProvider provider)
        
            try
            
                ISchedulerFactory schedFact = new StdSchedulerFactory();
                IScheduler sched = schedFact.GetScheduler();
                sched.Start();

                IJobDetail job = JobBuilder.Create<ProcessTracksJob>()
                    .WithIdentity("ProcessTracks", "Group1")
                    .Build();

                job.JobDataMap["tracksService"] = provider.GetService(typeof(ITracksService));
                job.JobDataMap["cloudService"] = provider.GetService(typeof(ICloudService));
                job.JobDataMap["broadcastService"] = provider.GetService(typeof(IBroadcastService));
                job.JobDataMap["categoriesService"] = provider.GetService(typeof(ICategoriesService));

                ITrigger trigger = TriggerBuilder.Create()
                    .WithSimpleSchedule(x => x
                        .RepeatForever()
                        .WithInterval(TimeSpan.FromDays(1)))
                    .StartNow()
                    .Build();

                sched.ScheduleJob(job, trigger);

            
            catch (ArgumentException e)
            
                // Log the error
            
        
   

还有工作:

public async void Execute(IJobExecutionContext context)

    var tracksService = context.MergedJobDataMap["tracksService"] as ITracksService;
    var cloudService = context.MergedJobDataMap["cloudService"] as ICloudService;
    var broadcastService = context.MergedJobDataMap["broadcastService"] as IBroadcastService;
    var categoryService = context.MergedJobDataMap["categoriesService"] as ICategoriesService;

    if (tracksService == null || cloudService == null || broadcastService == null || categoryService == null)
        return;

    var broadcasts = broadcastService.GetAll();
    // Some more work to do.

所以这个按预期工作,我很满意!但这里的问题是,当我试图构建一个触发器以使该作业仅从我的 Web API 控制器启动一次时:

[Route("Proccess")]
[HttpPost]
public async Task<IActionResult> ProccessTracks()

    ProcessTracksScheduler.StartOnce(serviceProvider);
    return Ok("Job scheduled");

StartOnce方法,基本和Start一样:

public static void StartOnce(IServiceProvider provider)

    try
    
        ISchedulerFactory schedFact = new StdSchedulerFactory();
        IScheduler sched = schedFact.GetScheduler();
        sched.Start();

        IJobDetail job = JobBuilder.Create<ProcessTracksJob>()
            .WithIdentity("ProcessTracksOnce", "Group1")
            .Build();

        job.JobDataMap["tracksService"] = provider.GetService(typeof(ITracksService));
        job.JobDataMap["cloudService"] = provider.GetService(typeof(ICloudService));
        job.JobDataMap["broadcastService"] = provider.GetService(typeof(IBroadcastService));
        job.JobDataMap["categoriesService"] = provider.GetService(typeof(ICategoriesService));

        ITrigger trigger = TriggerBuilder.Create()
            .StartNow()
            .Build();

        sched.ScheduleJob(job, trigger);

    
    catch (ArgumentException e)
    
        // Log the error
    

但是当我的工作被执行时,var broadcasts = broadcastService.GetAll(); 行抛出一个异常,说我的 DbContext 已被释放..

DbContext 通过 DI 传递到使用 ASP.Net-Core 的控制器/服务:

// In Startup.cs
public void ConfigureServices(IServiceCollection services)

    services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(this.Configuration.GetConnectionString("MyConnString")));
    services.AddTransient<IBroadcastService, BroadcastsService>();
    // Add the others services.        

我尝试了很多东西,但找不到任何解决此问题的方法。

【问题讨论】:

您的 Quartz 代码是否在您的 ASP.NET 应用程序中运行?如果是这样,你不应该这样做。 ASP.NET 用于短时间运行的任务,然后就结束了。通过在其中扩展它(例如运行 Quartz),您违背了 ASP.NET 的原则。只要它的生命周期结束,它就会终止。相反,您的应用程序应该与包含您的 Quartz 代码的专用进程通信。 我的 Quartz 代码在另一个项目中,而不是在我的 ASP.NET 应用程序中。你是这个意思吗? 该项目是否正在创建可执行文件? 不,你是对的。石英作业在 ASP.NET 应用程序中执行。不过暂时不成问题,工作一点也不繁重。 不管它有多重,您都无法控制 ASP.NET 和 IIS 何时回收应用程序池,或者任何数量的可能性。任何运行超过几秒钟的东西都有可能被终止。所以你可以想象,试图安排某事在未来运行一整天是行不通的。 【参考方案1】:

您是否尝试在构造函数中要求 ProcessTracksJob 类中的服务?我就是这样用的,没问题。

插入使用JobDataMap试试这样:

public class ProcessTracksJob :IJob 

   private readonly ITracksService _tracksService;

   public ProcessTracksJob(ITracksService tracksService, etc.) 
   
     _tracksService = tracksService;
     ...
   

   /// the execute method using the private fields in this class


【讨论】:

如果您的类派生自IJob,那么它将毫无问题地执行该方法。此外,请记住在您的 DI 库中注册它,以便能够在构造函数中注入这些服务。我正在使用这种方法没有问题。您遇到任何错误吗? 完全没有错误,在执行方法中放了一个断点,但它不会命中它。通过在我的DI库中注册它,你的意思是这样的:services.AddTransient&lt;IJob, ProcessTracksJob&gt;(); in @987654326 @方法? 对于 DI,我使用这样的 Job 类:builder.RegisterType&lt;ProcessTracksJob&gt;().AsSelf();(Autofac)。石英配置对我来说是一样的。对于测试,我为触发器使用了更小的时间跨度。 我不想为此使用 Autofac,因为 Asp.NET-core 已经处理 DI... 可能是寿命已经过期了,你试过不同的寿命服务吗? (作用域,单例)

以上是关于在 Asp.NET core 中触发 Quartz 作业时释放上下文的主要内容,如果未能解决你的问题,请参考以下文章

如何强制 Quartz 现在在 ASP.NET Core 中启动?

在 ASP.NET Core 应用程序中禁用 Quartz.Net 的调试日志记录

ASP.NET Core2.2+Quartz.Net 实现web定时任务

如何在 ASP.Net Core 中使用 IHostedService

自定义客户端验证 jQuery 适配器不会在 ASP.Net Core 中触发

asp.net core 2.0 授权在身份验证(JWT)之前触发