我有 一个这样的案例:有一个作业需要1个月运行一次,但是运行的时间是从数据库中读出来的,每个月执行的时间都不固定。比如:4月份执行的时间是4.30 18:00, 5月份执行的时间是6.2 19:00, 6月份执行的时间是6:30 18:00。一年12个月的运行时间都不固定。但是我又希望这个任务1个月运行一次,且只有一个计时器在用。
执行作业可以有很多种法子,1. MSSql Server提供了作业,可以自己创建作业来运行。2. Windows系统的计划任务。 3. Timer计时器。 4. Quartz.net
但是这些基本都是提供按时间间隔,固定的时间点(每天,每月,每年)执行。这些任务的执行时间都是作为配置文件在任务开始运行时就加载进去了,以后就一直按着这个点进行。若想修改调度的时间点,就只能修改配置了,然后重新启动调度程序。但是总不能每个月都来修改一次吧。那就不叫流水作业,全人工去搞算了。
在网上搜了一些资料,有点收获。用Quartz.net吧。发现它有个Reschedule的函数,可以重新规划任务的执行。但是我暂时没用。用了其他的。
Quartz.net 必须的dll有Quartz.dll,Common.Logging,Common.Logging.log4Net,C5,这些dll的版本一定要匹配。姐就深受其苦,版本不配套。整了很久。
public class QuartzManage { private static ISchedulerFactory sf = new StdSchedulerFactory(); private static String JOB_GROUP_NAME = "group"; private static String TRIGGER_GROUP_NAME = "trigger"; private static IScheduler sched; public static void StartJob(String jobName, Type jobType, String time) { sched = sf.GetScheduler(); JobDetailImpl jobDetail = new JobDetailImpl(); jobDetail.Name = jobName; jobDetail.Group = JOB_GROUP_NAME; jobDetail.JobType = jobType; CronTriggerImpl trigger = new CronTriggerImpl(jobName, TRIGGER_GROUP_NAME); trigger.CronExpressionString = time; sched.ScheduleJob(jobDetail, trigger); if (!sched.IsShutdown) { sched.Start(); } } /** * 从Scheduler 移除当前的Job,修改Trigger * * @param jobDetail * @param time * @throws SchedulerException * @throws ParseException */ public static void ModifyJobTime(ITrigger trigger, IJobDetail jobDetail, String time) { sched = sf.GetScheduler(); if (trigger != null) { CronTriggerImpl ct = (CronTriggerImpl)trigger; // 移除当前进程的Job sched.DeleteJob(jobDetail.Key); // 修改Trigger ct.CronExpressionString = time; Console.WriteLine("CronTrigger getName " + ct.JobName); // 重新调度jobDetail sched.ScheduleJob(jobDetail, ct); } } public static void ShutDownJob() { if (sched != null && !sched.IsShutdown) { sched.Shutdown(); } } }
测试的Job:
public class SimpleQuartzJob:IJob { private static ILog _log = LogManager.GetLogger(typeof(SimpleQuartzJob)); private static int a = 1; #region IJob 成员 public void Execute(IJobExecutionContext context) { try { a++; string jobName = context.JobDetail.Key.Name; _log.Info("Execution job:" + jobName + " executing at " + DateTime.Now.ToString()); Console.WriteLine("Execution job:" + jobName + " executing at " + DateTime.Now.ToString()); if (a==3) { QuartzManage.ModifyJobTime(context.Trigger, context.JobDetail, "0/5 * * * * ?"); } } catch (Exception e) { _log.Error("---Error in job!"); Console.WriteLine(e.StackTrace); JobExecutionException e2 = new JobExecutionException(e); e2.RefireImmediately = true; throw e2; } } #endregion }
主程序
class Program { static void Main(string[] args) { Console.WriteLine(DateTime.Now.ToString()); //间隔3秒执行1次 QuartzManage.StartJob("job1", typeof(SimpleQuartzJob), "0/3 * * * * ?"); //"0 1 12 15 5 ?" Thread.Sleep(20000); QuartzManage.ShutDownJob(); Console.ReadKey(); } }
<?xml version="1.0"?> <configuration> <configSections> <sectionGroup name="common"> <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"/> </sectionGroup> </configSections> <common> <logging> <factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4net"> <arg key="configType" value="FILE"/> <!-- FILE,FILE-WATCH,INLINE,EXTERNAL--> <arg key="configFile" value="~/log4net.config"/> <arg key="level" value="Warn"/> </factoryAdapter> </logging> </common> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Common.Logging" publicKeyToken="AF08829B84F0328E" culture="neutral"/> <bindingRedirect oldVersion="0.0.0.0-2.1.2.0" newVersion="2.1.2.0"/> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
log4net.config 配置log的
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" /> </configSections> <appSettings> </appSettings> <log4net> <!--定义输出到文件中--> <appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender"> <!--输出日志文件的路径--> <file value="Log\TestLog.log" /> <!--输出日志时自动向后追加--> <appendToFile value="true" /> <!--防止多线程时不能写Log,官方说线程非安全,但实际使用时,本地测试正常,部署后有不能写日志的情况--> <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> <!--置为true,当前最新日志文件名永远为file节中的名字--> <staticLogFileName value="false" /> <!--日志以大小作为备份样式,还有一种方式是Date(日期)--> <rollingStyle value="size" /> <countDirection value="-1" /> <!--单个日志的最大容量,(可用的单位:KB|MB|GB)不要使用小数,否则会一直写入当前日志--> <maximumFileSize value="1MB" /> <!--日志最大个数,都是最新的--> <maxSizeRollBackups value="10" /> <datePattern value='"."yyyy-MM-dd".log"' /> <layout type="log4net.Layout.PatternLayout"> <!--每条日志末尾的文字说明--> <footer value="**************************************************************" /> <!--输出格式--> <!--样例:2008-03-26 13:42:32,111 [10] INFO Log4NetDemo.MainClass - info--> <conversionPattern value="%newline%d{yyyy/MM/dd,HH:mm:ss.fff},[%-5level]%newline Message:%message%newline" /> </layout> </appender> <root> <!--文件形式记录日志--> <appender-ref ref="LogFileAppender" /> </root> </log4net> </configuration>
关于前面讲到的案例,可以在最初时设置4.30 18:00执行,比如在4.30 18:00执行完时,就再次设置调度的时间改为6.2 18:00执行,这次执行完就设置下次执行的时间。
至于有什么不好的影响。暂时不清楚。
但是这个法子有个缺陷。比如在4.30 18:00执行完时就设置下个月的执行时间6.2 18:00(从数据库读取的),那么有可能用户在5.10又将5月份的执行时间改为5.29 18:00,而不再是6.2 18:00。调度程序已经设置了下次的调度时间,不会再去读5月份的执行时间了。这样就只能手动就重启调度程序。不能一劳永逸。
思路可以借鉴。。。。。。。