You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在ASP.NET MVC中使用Quartz.NET停止当前任务并启动新任务

如何在Quartz.NET中定时停止旧爬虫任务并启动新任务?

我来帮你搞定这个问题!基于你现有的ASP.NET MVC + Quartz.NET代码,我们可以通过两个核心步骤实现需求:停止运行中的旧任务,以及让新任务自动在5小时后停止(或者手动触发新任务时终止旧任务)。下面是具体的实现方案:

一、核心思路

Quartz.NET中管理任务的关键是通过JobKeyTriggerKey来定位任务。我们需要:

  • 在启动新任务前,检查并清理旧任务;
  • 给新任务设置自动停止的时间(直接给触发器加结束时间是最简洁的方式)。

二、修改你的代码

1. 先处理旧任务的停止逻辑

在你的Index方法中,每次创建新任务前,先停止并删除旧的爬虫任务:

[HttpPost]
public ActionResult Index(string keyword, string url)
{
    // 获取调度器(建议全局单例,这里保持你的原有写法)
    IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
    
    // 步骤1:停止并删除旧任务
    var oldJobKey = new JobKey("MyScrapJob");
    if (scheduler.CheckExists(oldJobKey))
    {
        // 先暂停该任务的所有触发器
        scheduler.PauseTriggers(GroupMatcher<TriggerKey>.AnyGroup().Where(t => t.JobKey == oldJobKey));
        // 删除旧任务
        bool isDeleted = scheduler.DeleteJob(oldJobKey);
        if (isDeleted)
        {
            Console.WriteLine("旧爬虫任务已成功停止并删除");
        }
    }
    
    // 步骤2:确保调度器已启动(避免重复启动)
    if (!scheduler.IsStarted)
    {
        scheduler.Start();
    }
    
    // 步骤3:创建新的爬虫任务
    IJobDetail job = JobBuilder.Create<ScrapJob>()
        .WithIdentity("MyScrapJob")
        .UsingJobData("url", url)
        .UsingJobData("keyword", keyword)
        .Build();
    
    // 步骤4:设置触发器,同时指定5小时后自动停止
    ITrigger trigger = TriggerBuilder.Create()
        .WithDailyTimeIntervalSchedule(
            s => s.WithIntervalInSeconds(20).OnEveryDay().StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(0, 0))
        )
        // 关键:设置任务结束时间为当前时间+5小时
        .EndAt(DateTimeOffset.Now.AddHours(5))
        .Build();
    
    // 调度新任务
    scheduler.ScheduleJob(job, trigger);
    
    return View(db.Scraps.ToList());
}

2. 修复Job类的设计问题

注意到你的ScrapJob既是Quartz的Job类,又包含数据实体和抓取逻辑,这容易造成混淆。建议拆分:

  • 把Quartz任务类命名为ScrapingJob
  • 数据实体类命名为Scrap
  • 抓取逻辑单独封装,避免在Job实例中直接操作DbContext(using包裹Job实例是错误的,Job应该由Quartz管理生命周期)。

修改后的Job类示例:

public class ScrapingJob : IJob
{
    public void Execute(IJobExecutionContext context)
    {
        JobDataMap dataMap = context.JobDetail.JobDataMap;
        string url = dataMap.GetString("url");
        string keyword = dataMap.GetString("keyword");
        
        // 单独处理抓取逻辑,使用using管理DbContext生命周期
        using (var db = new YourDbContext()) // 替换成你的实际DbContext类
        {
            PerformScraping(url, keyword, db);
        }
    }
    
    private void PerformScraping(string url, string keyword, YourDbContext db)
    {
        int count = 0;
        Scrap scrap = null;
        HtmlDocument doc = new HtmlDocument();
        
        try
        {
            var request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "GET";
            using (var response = (HttpWebResponse)request.GetResponse())
            using (var stream = response.GetResponseStream())
            {
                doc.Load(stream, Encoding.GetEncoding("UTF-8"));
                foreach (HtmlNode node in doc.DocumentNode.SelectNodes("//text()") ?? Enumerable.Empty<HtmlNode>())
                {
                    if (node.InnerText.Contains(keyword))
                    {
                        count++;
                        scrap = new Scrap
                        {
                            Keyword = keyword,
                            DateTime = DateTime.Now.ToString(),
                            Count = count,
                            Url = url
                        };
                    }
                }
            }
        }
        catch (WebException ex)
        {
            Console.WriteLine($"抓取异常:{ex.Message}");
        }
        
        // 保存数据前判空,避免空引用
        if (scrap != null)
        {
            var existingId = db.Scraps
                .Where(s => s.Keyword == keyword && s.Count == scrap.Count)
                .Max(s => (int?)s.Id) ?? 0;
            
            if (existingId == 0)
            {
                db.Scraps.Add(scrap);
                db.SaveChanges();
            }
        }
    }
}

三、额外注意事项

  • 调度器单例化:建议在应用启动时(比如Global.asax或Startup类)初始化一次调度器,避免每次请求都创建新实例;
  • 任务标识唯一性:如果需要同时运行多个爬虫任务,不要固定使用"MyScrapJob"作为JobKey,应该用动态生成的Key(比如结合keyword和url);
  • 异常处理:在停止任务和调度任务时,可以添加try-catch块处理Quartz相关的异常,比如调度器未启动、任务不存在等。

内容的提问来源于stack exchange,提问作者behnam

火山引擎 最新活动