基于Quartz.NET与C#的定时邮件任务生产环境失效求助
问题描述
基于Quartz.NET与C#开发定时任务调度器,每10分钟执行一次,检测到EmailSendData表存在未发送数据时发送邮件。本地及测试环境运行正常,但部署到生产环境后任务无法触发。通过EmailSendLogs表确认Global.asax的Application_Start已执行日志插入,但JobScheduler.Start()未触发任务执行。
相关代码
JobScheduler.cs
public static void Start() { IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler(); scheduler.Start(); IJobDetail job = JobBuilder.Create<EmailTrigger>().Build(); ITrigger trigger = TriggerBuilder.Create() .WithDailyTimeIntervalSchedule (s => s.WithIntervalInMinutes(10) .OnEveryDay() .InTimeZone(TimeZoneInfo.Local) //added later ) .Build(); scheduler.ScheduleJob(job, trigger); }
Global.asax.cs
protected void Application_Start(object sender, EventArgs e) { risknucleus.DataLayer.cls_DataLayer obj = new risknucleus.DataLayer.cls_DataLayer(); obj.execute("insert into EmailSendLogs(Message,Date) values('Global Page Hit',getdate())"); JobScheduler.Start(); }
EmailTrigger.cs
public void Execute(IJobExecutionContext context) { try { clsEmail objEmail = new clsEmail(); if (dtLayer.getValueFromStaticConfiguration("EmailTriggerOnApproval", "N") == "Y") { dtLayer.execute(@"Insert into EmailSendLogs(Message,Description,Date) values ('Trigger started 1','Trigger started 1' , GetDate())"); DataTable dtData = dtLayer.getDataTableFromQuery("select * from EmailSendData where IsSend is null"); if (dtData.Rows.Count > 0) { for (int i = 0; i < dtData.Rows.Count; i++) { dtLayer.execute(@"Insert into EmailSendLogs(Message,Description,Date) values ('Trigger started 2','Trigger started 2' , GetDate())"); string NomineeList = dtData.Rows[i]["NomineeUser"].ToString(); string FromEmpid = dtData.Rows[i]["FromEmpID"].ToString(); string EmailID = dtData.Rows[i]["EmailID"].ToString(); string url = dtData.Rows[i]["URL"].ToString(); string Type = dtData.Rows[i]["Type"].ToString(); string query = dtData.Rows[i]["EmailDetails"].ToString(); string UserID = dtData.Rows[i]["UserID"].ToString(); string NotificationID = dtData.Rows[i]["NotificationID"].ToString(); string CCMail = dtData.Rows[i]["CCMail"].ToString(); string ApprovalCode = dtData.Rows[i]["ApprovalCode"].ToString(); string Id = dtData.Rows[i]["ID"].ToString(); DataTable dtEmailDetails = dtLayer.getDataTableFromQuery(query); ArrayList emailDetails = dtLayer.datatableTOarraylist(dtEmailDetails); emailDetails.Add(ApprovalCode); if (dtLayer.getValueFromStaticConfiguration("SendEmail", "N") == "Y") objEmail.sendMailNewFunction(NomineeList, FromEmpid, EmailID, url, Type, "", emailDetails, UserID, false, null, NotificationID, CCMail); dtLayer.execute("update EmailSendData set isSend='Y' where ID= " + Id + " "); } } else { dtLayer.execute(@"Insert into EmailSendLogs(Message,Description,Date) values ('Job ran successfully','No approved cases pending' , GetDate())"); } } } catch (Exception e) { dtLayer.execute(@"Insert into EmailSendLogs(Message,Description,Date) values (" + dtLayer.checkNull(e.ToString()) + ",'Exception Catch',GetDate())"); throw e; } }
已尝试的解决方法
- 重启IIS服务器并清除缓存
- 设置应用程序池模式为
AlwaysRunning - 为触发器添加时区配置
排查与解决建议
1. 避免Scheduler被GC回收
当前代码中IScheduler是局部变量,Start()方法执行完毕后可能被垃圾回收器回收,导致任务停止调度。修改JobScheduler类,将IScheduler定义为静态全局变量:
public class JobScheduler { private static IScheduler _scheduler; public static void Start() { _scheduler = StdSchedulerFactory.GetDefaultScheduler(); _scheduler.Start(); IJobDetail job = JobBuilder.Create<EmailTrigger>().Build(); ITrigger trigger = TriggerBuilder.Create() .WithDailyTimeIntervalSchedule(s => s.WithIntervalInMinutes(10) .OnEveryDay() .InTimeZone(TimeZoneInfo.Local) ) .Build(); _scheduler.ScheduleJob(job, trigger); } }
2. 捕获Scheduler初始化异常
当前仅在EmailTrigger.Execute()中记录异常,但如果Scheduler初始化或任务调度时发生异常,不会触发Execute(),导致无日志记录。在JobScheduler.Start()中添加异常捕获:
public static void Start() { try { _scheduler = StdSchedulerFactory.GetDefaultScheduler(); _scheduler.Start(); IJobDetail job = JobBuilder.Create<EmailTrigger>().Build(); ITrigger trigger = TriggerBuilder.Create() .WithDailyTimeIntervalSchedule(s => s.WithIntervalInMinutes(10) .OnEveryDay() .InTimeZone(TimeZoneInfo.Local) ) .Build(); _scheduler.ScheduleJob(job, trigger); // 记录Scheduler启动成功日志 var dtLayer = new risknucleus.DataLayer.cls_DataLayer(); dtLayer.execute("Insert into EmailSendLogs(Message,Description,Date) values ('Scheduler started','Quartz调度器初始化完成', GetDate())"); } catch (Exception ex) { var dtLayer = new risknucleus.DataLayer.cls_DataLayer(); dtLayer.Execute($"Insert into EmailSendLogs(Message,Description,Date) values ('{dtLayer.checkNull(ex.ToString())}','调度器初始化失败', GetDate())"); throw; } }
3. 完善IIS应用池配置
除了设置AlwaysRunning,还需确认以下配置:
- 将应用池的Idle Time-out设为0,避免应用池因闲置被回收
- 开启网站的Preload Enabled(在网站高级设置中),确保IIS自动启动应用,无需等待首次请求
- 确认应用池的Start Mode为
AlwaysRunning
4. 检查生产环境配置项
验证生产环境的静态配置EmailTriggerOnApproval是否设为Y,如果为N,EmailTrigger.Execute()中的核心逻辑不会执行,看起来像是任务未触发。
5. 配置Quartz详细日志
Quartz默认日志输出较简洁,建议配置日志框架(如log4net、NLog)输出Quartz的调试日志,查看Scheduler初始化、任务触发的详细过程,定位是否有隐藏异常。
6. 验证权限与环境差异
- 检查应用池运行身份是否拥有数据库读写权限、SMTP服务器访问权限
- 确认生产环境数据库连接字符串正确,无防火墙或网络限制
- 核对生产服务器时区与测试环境是否一致,避免触发时间不符合预期
7. 检查应用池回收设置
确认IIS应用池的回收规则(如固定时间回收),若开启回收,需确保Preload Enabled已开启,否则回收后应用需等待请求才会重启,导致任务中断。
内容的提问来源于stack exchange,提问作者Unknown

