我正在使用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秒)。
作业配置为不允许并发执行。
我想要实现以下目标:
问题:我是否正确管理了预定的工作,还是可以进行改进?