偶然發現項目中有多個需要定時完成的任務,通過以下方式進行的。
- 有一些是寫在MSSql Server的作業中,這些是純粹滴操作數據庫的行爲。
- 有一些是寫在Window Service中,用Timer定時器來輪詢完成。既包括操作數據庫,又包括髮郵件,或者同步數據去其他系統。
<?xml version="1.0" encoding="utf-8" ?>
<JobScheduler>
<Job Description="作業1">
<DllName>JobLibrary.dll</DllName>
<JobDetail job="test1" group="test1Group" jobtype="JobLibrary.FirstJob" />
<Trigger name="test1" group="test1Group" type="CronTrigger" expression="0/2 * * * * ?" />
</Job>
<Job Description="作業2">
<DllName>JobLibrary.dll</DllName>
<JobDetail job="test2" group="test2Group" jobtype="JobLibrary.SecondJob" />
<Trigger name="test2" group="test2Group" type="CronTrigger" expression="0/5 * * * * ?" />
</Job>
<Job Description="作業3">
<DllName>JobLibrary.dll</DllName>
<JobDetail job="test3" group="test3Group" jobtype="JobLibrary.ThirdJob" />
<Trigger name="test3" group="test3Group" type="CronTrigger" expression="0/3 * * * * ?" />
</Job>
</JobScheduler>
統一調度的主程序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Quartz.Impl;
using Quartz;
using Quartz.Impl.Triggers;
using System.Reflection;
using System.IO;
using Common.Logging;
namespace JobLibrary
{
public class JobManage
{
private static ISchedulerFactory sf = new StdSchedulerFactory();
private static IScheduler scheduler;
static readonly ILog errorLog = LogManager.GetLogger("LogFileAppender");
public static void StartScheduleFromConfig()
{
string currentDir = AppDomain.CurrentDomain.BaseDirectory;
try
{
XDocument xDoc = XDocument.Load(Path.Combine(currentDir, "JobScheduler.config"));
var jobScheduler = from x in xDoc.Descendants("JobScheduler") select x;
var jobs = jobScheduler.Elements("Job");
XElement jobDetailXElement, triggerXElement;
scheduler = sf.GetScheduler();
CronTriggerImpl cronTrigger;
foreach (var job in jobs)
{
//加載程序集joblibaray
Assembly ass = Assembly.LoadFrom(Path.Combine(currentDir, job.Element("DllName").Value));
jobDetailXElement = job.Element("JobDetail");
triggerXElement = job.Element("Trigger");
JobDetailImpl jobDetail = new JobDetailImpl(jobDetailXElement.Attribute("job").Value,
jobDetailXElement.Attribute("group").Value,
ass.GetType(jobDetailXElement.Attribute("jobtype").Value));
if (triggerXElement.Attribute("type").Value.Equals("CronTrigger"))
{
cronTrigger = new CronTriggerImpl(triggerXElement.Attribute("name").Value,
triggerXElement.Attribute("group").Value,
triggerXElement.Attribute("expression").Value);
scheduler.ScheduleJob(jobDetail, cronTrigger);
}
}
scheduler.Start();
}
catch (Exception e)
{
errorLog.Error(e.StackTrace);
}
}
public static void ShutDown()
{
if (scheduler != null && !scheduler.IsShutdown)
{
scheduler.Shutdown();
}
}
/**
* 從Scheduler 移除當前的Job,修改Trigger
*
* @param jobDetail
* @param time
* @throws SchedulerException
* @throws ParseException
*/
public static void ModifyJobTime(IJobExecutionContext jobExecution, String time)
{
scheduler = jobExecution.Scheduler;
ITrigger trigger = jobExecution.Trigger;
IJobDetail jobDetail = jobExecution.JobDetail;
if (trigger != null)
{
CronTriggerImpl ct = (CronTriggerImpl)trigger;
// 移除當前進程的Job
scheduler.DeleteJob(jobDetail.Key);
// 修改Trigger
ct.CronExpressionString = time;
Console.WriteLine("CronTrigger getName " + ct.JobName);
// 重新調度jobDetail
scheduler.ScheduleJob(jobDetail, ct);
}
}
}
}
以下是3個Job:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.Logging;
namespace JobLibrary
{
public abstract class JobBase
{
/// <summary>
/// JOB狀態日誌
/// </summary>
protected internal static readonly ILog jobStatus = LogManager.GetLogger("LogFileAppender");
/// <summary>
/// 服務錯誤日誌
/// </summary>
protected internal static readonly ILog serviceErrorLog = LogManager.GetLogger("LogFileAppender");
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Quartz;
namespace JobLibrary
{
public class FirstJob : JobBase, IJob
{
#region IJob 成員
public void Execute(IJobExecutionContext context)
{
jobStatus.Info("--------1111first job start ----------");
try
{
jobTest();
}
catch (Exception e)
{
serviceErrorLog.Info(string.Concat("first job:", e.StackTrace));
}
jobStatus.Info("---------first job Complete ----------");
}
#endregion
public void jobTest()
{
jobStatus.Info("weeksetting test1!");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Quartz;
namespace JobLibrary
{
public class SecondJob:JobBase,IJob
{
#region IJob 成員
public void Execute(IJobExecutionContext context)
{
jobStatus.Info("------------2222 second job start -----------");
try
{
jobTest();
}
catch (Exception e)
{
serviceErrorLog.Info(String.Concat("second job:", e.StackTrace));
}
jobStatus.Info("------------ second job Complete -----------");
}
#endregion
public void jobTest()
{
jobStatus.Info("weeksetting test2!");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Quartz;
namespace JobLibrary
{
public class ThirdJob : JobBase, IJob
{
private static int a = 1;
#region IJob 成員
public void Execute(IJobExecutionContext context)
{
jobStatus.Info("--------3333Third job start ----------");
try
{
if (!context.JobDetail.JobDataMap.Contains("a"))
{
context.JobDetail.JobDataMap.Add("a", a);
}
else
{
context.JobDetail.JobDataMap["a"] = a;
}
jobTest();
jobStatus.Info("a=" + context.JobDetail.JobDataMap["a"]);
if (a == 3)
{
JobManage.ModifyJobTime(context, "0/5 * * * * ?");
}
jobStatus.Info("a=" + a);
a++;
}
catch (Exception e)
{
serviceErrorLog.Info(string.Concat("Third job:", e.StackTrace));
}
jobStatus.Info("---------Third job Complete ----------");
}
#endregion
public void jobTest()
{
jobStatus.Info("weeksetting test3!");
}
}
}
以下是2個配置文件App.config,log4net.config,用於配置Log的:
App.config
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"/>
</sectionGroup>
</configSections>
<common>
<logging>
<!--1.此Adapter只輸出到控制檯--><!--
<factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
<arg key="level" value="INFO" />
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
</factoryAdapter>-->
<!--2.此Adapter只輸出到Log4.net的配置文件所指定的地方-->
<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"/> <!-- 指定log4net的配置文件名稱 -->
<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
<?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\XXLog.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>
使用時,在Global.asax.cs中增加這樣:
protected void Application_Start(object sender, EventArgs e)
{
JobManage.StartScheduleFromConfig();
}
protected void Application_End(object sender, EventArgs e)
{
JobManage.Shutdown();
}
如有新的任務加入,只需在JobScheduler.config中配置新任務項。若要停止某個任務,只需註釋掉任務項,然後重啓應用程序
application改變的條件:
1. iis重啓
2. 代碼改變
3. 應用程序池回收時間到了
4. 應用程序池空閒時間到了
符合以上條件後,第一個request過來時會調用Application_Start。其中3和4是可以配置的(應用程序池屬性 -> 性能)
把所有的Job、JobManage主調度程序都放到一個工程JobLibrary,這樣便於管理,一個Job對於一個類。要移植時,只需把該類庫搬走。