You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Quartz.NET与C#的定时邮件任务生产环境失效求助

Quartz.NET定时任务生产环境失效排查与解决建议

问题描述

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 06:05:01