在ASP.NET MVC中使用Quartz.NET停止当前任务并启动新任务
如何在Quartz.NET中定时停止旧爬虫任务并启动新任务?
我来帮你搞定这个问题!基于你现有的ASP.NET MVC + Quartz.NET代码,我们可以通过两个核心步骤实现需求:停止运行中的旧任务,以及让新任务自动在5小时后停止(或者手动触发新任务时终止旧任务)。下面是具体的实现方案:
一、核心思路
Quartz.NET中管理任务的关键是通过JobKey和TriggerKey来定位任务。我们需要:
- 在启动新任务前,检查并清理旧任务;
- 给新任务设置自动停止的时间(直接给触发器加结束时间是最简洁的方式)。
二、修改你的代码
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




