如何为长时间运行的进程正确实现Quartz.Net作业生命周期?

时间:2017-01-27 12:23:09

标签: c# asp.net application-pool quartz.net-2.0

我正在使用Quartz.Net在IIS工作进程(IIS 8.5)中实现一些异步处理。一项特殊工作可能需要10分钟才能运行并进行大量处理。

以下代码说明了我如何处理工作生命周期。

计划定义

public class JobScheduler
{
    private static IScheduler _quartzScheduler;

    public static void Start()
    {
        _quartzScheduler = new StdSchedulerFactory().GetScheduler();
        _quartzScheduler.JobFactory = new NinjectJobFactory();

        ScheduleTheJob(_quartzScheduler);

        _quartzScheduler.Context.Add("key", "scheduler");
        _quartzScheduler.Start();
    }

    private static void ScheduleTheJob(IScheduler scheduler)
    {
        IJobDetail job = JobBuilder.Create<JobClass>().UsingJobData("JobKey", "JobValue").Build();

        ITrigger trigger = TriggerBuilder.Create()
            .WithIdentity("JobTrigger", "JobGroup").UsingJobData("JobKey", "JobTrigger").StartNow()
            .WithSimpleSchedule(x => x
                // 30 minutes for refresh period
                .WithIntervalInSeconds(ConfigService.JobRefreshPeriod)
                .RepeatForever())
            .Build();

        scheduler.ScheduleJob(job, trigger);
    }

    public static void StopScheduler(bool waitForCompletion)
    {
        _quartzScheduler.Shutdown(waitForCompletion);
    }
}

应用程序池关闭处理

public class ApplicationPoolService : IApplicationPoolService
{
    public bool IsShuttingDown()
    {
        return System.Web.Hosting.HostingEnvironment.ShutdownReason != ApplicationShutdownReason.None;
    }

    public ApplicationShutdownReason GetShutdownReason()
    {
        return System.Web.Hosting.HostingEnvironment.ShutdownReason;
    }
}

public class HostingEnvironmentRegisteredObject : IRegisteredObject
{
    public void Stop(bool immediate)
    {
        if (immediate)
            return;

        JobScheduler.StopScheduler(waitForCompletion: true);

        var logger = NinjectWebCommon.Kernel.Get<ILoggingService>();
        var appPoolService = NinjectWebCommon.Kernel.Get<IApplicationPoolService>();
        var reason = appPoolService.GetShutdownReason().ToString();
        logger.Log(LogLevel.Info, $"HostingEnvironmentRegisteredObject.stop called with shutdown reason {reason}");
    }
}

Global.asax.cs连接

protected void Application_Start()
{
    JobScheduler.Start();

    HostingEnvironment.RegisterObject(new HostingEnvironmentRegisteredObject());
}

protected void Application_Error()
{
    Exception exception = Server.GetLastError();
    Logger.Log(LogLevel.Fatal, exception, "Application global error");
}

protected void Application_End(object sender, EventArgs e)
{
    // stopping is now triggered in HostingEnvironmentRegisteredObject 
    // JobScheduler.StopScheduler(false);

    // get shutdown reason
    var appPoolService = NinjectWebCommon.Kernel.Get<IApplicationPoolService>();
    var reason = appPoolService.GetShutdownReason().ToString();
    Logger.Log(LogLevel.Info, $"Application_End called with shutdown reason {reason}");
}

工作步骤说明

if (ApplicationPoolService.IsShuttingDown())
{
    Logger.Log(LogLevel.Info, "(RefreshEnvironmentImportingSystemData) Application pool is shutting down");
    return;
}

// about 20-30 steps may be here
environments.ForEach(env => 
{
    if (ApplicationPoolService.IsShuttingDown())
         return;

    // do heavy processing for about 2 minutes (worst case), typically some 10-20s
}

// one job step may allocate several hundreds of MB, so GC is called to reclaim some memory sooner 
// it takes a few seconds (worst case)
GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();
GC.Collect();

最初,我在调用Application_End时停止了调度程序,但我意识到在应用程序池即将被杀死时调用了它,所以当应用程序池被通知其关闭已经启动时,我会移动它。

我已将应用程序池的默认值保留为关闭时间限制(90秒)。

作业配置为不允许并发执行。

我想要实现以下目标:

  • 避免在实际执行期间强行杀死工作
  • 最小化两个工作进程同时运行的时间(与刚开始处理新请求的进程并行关闭一个)

问题:我是否正确管理了预定的工作,还是可以进行改进?

0 个答案:

没有答案