Quartz.NET是一個開源的作業調度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#寫成,可用於winform和asp.net應用中。它提供了巨大的靈活性而不犧牲簡單性。你能夠用它來為執行一個作業而創建簡單的或復雜的調度。它有很多特征,如:數據庫支持,集群,插件,支持cron-like表達式等等
以上介紹是從博客園張善友(http://www.cnblogs.com/shanyou/archive/2007/08/25/quartznettutorial.html)的博客摘錄,可登錄他博客具體了解quartz.net。
我在這裡只講具體在項目中的實現:
通過Windows服務調用Quartz.net,然後Quartz.net 調用WinForm消息窗口,實現計劃任務消息推送。
【Windows服務】-->【Quartz.net】 --> 【Winform .exe】 --> 【程序打包】
項目解決方案,如圖所示:
1、打開vs2010--新建項目--選擇"Windows服務",我這裡命名為"QuartzService". 創建好後首先映入我們眼簾的是QuartzService.cs[設計]視圖,右鍵點設計視圖選擇"添加安裝程序",如下圖:
注釋:(創建一個Windows服務,僅用InstallUtil程序去安裝這個服務是不夠的。你必須還要把一個服務安裝程序添加到你的Windows服務當中,這樣便於InstallUtil或是任何別的安裝程序知道應用你服務的是怎樣的配置設置)
2、切換到剛被添加的ProjectInstaller的設計視圖, 設置serviceInstaller1組件的屬性: StartType = Automatic;ServiceName = QuartzService;
設置serviceProcessInstaller1組件的屬性 Account = LocalSystem; 如下圖:
3、QuartzService中添加Quartz.dll ,log4net.dll引用,在QuartzService.cs文件中引用命名空間 Quartz;Quartz.Impl; log4net;
右鍵點擊QuartzService項目,屬性-目標框架 ,選擇.net Framwork 4,如下圖:
4、添加app.config配置文件,具體配置如下:
1 <?xml version="1.0"?> 2 <configuration> 3 <configSections> 4 <section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/> 5 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/> 6 <sectionGroup name="common"> 7 <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"/> 8 </sectionGroup> 9 </configSections> 10 <common> 11 <logging> 12 <factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4net"> 13 <arg key="configType" value="INLINE"/> 14 </factoryAdapter> 15 </logging> 16 </common> 17 <log4net> 18 <appender name="InfoFileAppender" type="log4net.Appender.RollingFileAppender"> 19 <file value="log/" /> 20 <appendToFile value="true" /> 21 <param name="DatePattern" value="yyyyMMdd".txt"" /> 22 <rollingStyle value="Date" /> 23 <maxSizeRollBackups value="100" /> 24 <maximumFileSize value="1024KB" /> 25 <staticLogFileName value="false" /> 26 <Encoding value="UTF-8" /> 27 <filter type="log4net.Filter.LevelRangeFilter"> 28 <param name="LevelMin" value="INFO" /> 29 <param name="LevelMax" value="INFO" /> 30 </filter> 31 <layout type="log4net.Layout.PatternLayout"> 32 <conversionPattern value="%date %-5level %logger - %message%newline" /> 33 </layout> 34 </appender> 35 <appender name="ErrorFileAppender" type="log4net.Appender.RollingFileAppender"> 36 <file value="log/error.txt" /> 37 <appendToFile value="true" /> 38 <rollingStyle value="Size" /> 39 <maxSizeRollBackups value="100" /> 40 <maximumFileSize value="10240KB" /> 41 <staticLogFileName value="true" /> 42 <Encoding value="UTF-8" /> 43 <filter type="log4net.Filter.LevelRangeFilter"> 44 <param name="LevelMin" value="WARN" /> 45 <param name="LevelMax" value="FATAL" /> 46 </filter> 47 <layout type="log4net.Layout.PatternLayout"> 48 <conversionPattern value="%date %-5level %logger - %message%newline" /> 49 </layout> 50 </appender> 51 <root> 52 <level value="INFO" /> 53 <appender-ref ref="InfoFileAppender" /> 54 <appender-ref ref="ErrorFileAppender" /> 55 </root> 56 </log4net> 57 58 <!-- 59 We use quartz.config for this server, you can always use configuration section if you want to. 60 Configuration section has precedence here. 61 --> 62 <appSettings> 63 <!-- YYC.WebService URL地址 --> 64 <add key="URL" value="http://localhost:43093/WebService.asmx" /> 65 </appSettings> 66 <!-- 67 <quartz > 68 </quartz> 69 --> 70 <startup> 71 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> 72 </startup> 73 </configuration> View Code1、在解決方案中添加新項-選擇"類庫",我這裡命名為Quartz.Net.JobLibrary,Quartz.Net.JobLibrary用來實現多個"作業"類。Quartz.Net.JobLibrary類庫中添加Quartz.dll ,log4net.dll引用。
Quartz.Net.JobLibrary類庫中添加一個類Interop.cs,這個類是為了解決在Win7中出現【交互式檢測】彈窗,博客園李敬然(http://www.cnblogs.com/gnielee/archive/2010/04/07/session0-isolation-part1.html)的博客詳細談到 穿透Session 0 隔離,具體代碼如下:
1 using System; 2 using System.Runtime.InteropServices; 3 4 namespace Quartz.Net.JobLibrary 5 { 6 public class Interop 7 { 8 public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; 9 public static void ShowMessageBox(string message, string title) 10 { 11 int resp = 0; 12 WTSSendMessage( 13 WTS_CURRENT_SERVER_HANDLE, 14 WTSGetActiveConsoleSessionId(), 15 title, title.Length, 16 message, message.Length, 17 0, 0, out resp, false); 18 } 19 [DllImport("kernel32.dll", SetLastError = true)] 20 public static extern int WTSGetActiveConsoleSessionId(); 21 [DllImport("wtsapi32.dll", SetLastError = true)] 22 public static extern bool WTSSendMessage( 23 IntPtr hServer, 24 int SessionId, 25 String pTitle, 26 int TitleLength, 27 String pMessage, 28 int MessageLength, 29 int Style, 30 int Timeout, 31 out int pResponse, 32 bool bWait); 33 34 public static void CreateProcess(string app, string path) 35 { 36 IntPtr hToken; 37 IntPtr hDupedToken = IntPtr.Zero; 38 39 PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); 40 SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES(); 41 sa.Length = Marshal.SizeOf(sa); 42 43 STARTUPINFO si = new STARTUPINFO(); 44 si.cb = Marshal.SizeOf(si); 45 46 int dwSessionID = WTSGetActiveConsoleSessionId(); 47 bool result = WTSQueryUserToken(dwSessionID, out hToken); 48 49 if (!result) 50 { 51 ShowMessageBox("WTSQueryUserToken failed", "AlertService Message"); 52 } 53 54 result = DuplicateTokenEx( 55 hToken, 56 GENERIC_ALL_ACCESS, 57 ref sa, 58 (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, 59 (int)TOKEN_TYPE.TokenPrimary, 60 ref hDupedToken 61 ); 62 63 if (!result) 64 { 65 ShowMessageBox("DuplicateTokenEx failed", "AlertService Message"); 66 } 67 68 IntPtr lpEnvironment; 69 result = CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false); 70 71 if (!result) 72 { 73 ShowMessageBox("CreateEnvironmentBlock failed", "AlertService Message"); 74 } 75 76 result = CreateProcessAsUser( 77 hDupedToken, 78 path + app, 79 String.Empty, 80 ref sa, ref sa, 81 false, 0, IntPtr.Zero, 82 path, ref si, ref pi); 83 84 if (!result) 85 { 86 int error = Marshal.GetLastWin32Error(); 87 string message = String.Format("CreateProcessAsUser Error: {0}", error); 88 ShowMessageBox(message, "AlertService Message"); 89 } 90 91 if (pi.hProcess != IntPtr.Zero) 92 CloseHandle(pi.hProcess); 93 if (pi.hThread != IntPtr.Zero) 94 CloseHandle(pi.hThread); 95 if (hDupedToken != IntPtr.Zero) 96 CloseHandle(hDupedToken); 97 } 98 99 [StructLayout(LayoutKind.Sequential)] 100 public struct STARTUPINFO 101 { 102 public Int32 cb; 103 public string lpReserved; 104 public string lpDesktop; 105 public string lpTitle; 106 public Int32 dwX; 107 public Int32 dwY; 108 public Int32 dwXSize; 109 public Int32 dwXCountChars; 110 public Int32 dwYCountChars; 111 public Int32 dwFillAttribute; 112 public Int32 dwFlags; 113 public Int16 wShowWindow; 114 public Int16 cbReserved2; 115 public IntPtr lpReserved2; 116 public IntPtr hStdInput; 117 public IntPtr hStdOutput; 118 public IntPtr hStdError; 119 } 120 121 [StructLayout(LayoutKind.Sequential)] 122 public struct PROCESS_INFORMATION 123 { 124 public IntPtr hProcess; 125 public IntPtr hThread; 126 public Int32 dwProcessID; 127 public Int32 dwThreadID; 128 } 129 130 [StructLayout(LayoutKind.Sequential)] 131 public struct SECURITY_ATTRIBUTES 132 { 133 public Int32 Length; 134 public IntPtr lpSecurityDescriptor; 135 public bool bInheritHandle; 136 } 137 138 public enum SECURITY_IMPERSONATION_LEVEL 139 { 140 SecurityAnonymous, 141 SecurityIdentification, 142 SecurityImpersonation, 143 SecurityDelegation 144 } 145 146 public enum TOKEN_TYPE 147 { 148 TokenPrimary = 1, 149 TokenImpersonation 150 } 151 152 public const int GENERIC_ALL_ACCESS = 0x10000000; 153 154 [DllImport("kernel32.dll", SetLastError = true, 155 CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 156 public static extern bool CloseHandle(IntPtr handle); 157 158 [DllImport("advapi32.dll", SetLastError = true, 159 CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] 160 public static extern bool CreateProcessAsUser( 161 IntPtr hToken, 162 string lpApplicationName, 163 string lpCommandLine, 164 ref SECURITY_ATTRIBUTES lpProcessAttributes, 165 ref SECURITY_ATTRIBUTES lpThreadAttributes, 166 bool bInheritHandle, 167 Int32 dwCreationFlags, 168 IntPtr lpEnvrionment, 169 string lpCurrentDirectory, 170 ref STARTUPINFO lpStartupInfo, 171 ref PROCESS_INFORMATION lpProcessInformation); 172 173 [DllImport("advapi32.dll", SetLastError = true)] 174 public static extern bool DuplicateTokenEx( 175 IntPtr hExistingToken, 176 Int32 dwDesiredAccess, 177 ref SECURITY_ATTRIBUTES lpThreadAttributes, 178 Int32 ImpersonationLevel, 179 Int32 dwTokenType, 180 ref IntPtr phNewToken); 181 182 [DllImport("wtsapi32.dll", SetLastError = true)] 183 public static extern bool WTSQueryUserToken( 184 Int32 sessionId, 185 out IntPtr Token); 186 187 [DllImport("userenv.dll", SetLastError = true)] 188 static extern bool CreateEnvironmentBlock( 189 out IntPtr lpEnvironment, 190 IntPtr hToken, 191 bool bInherit); 192 } 193 } View CodeQuartz.Net.JobLibrary類庫中再分別添加兩個"作業"類JobTest1.cs,JobTest2.cs:
代碼如下
JobTest1.cs
1 using System; 2 using System.Configuration; 3 using System.Reflection; 4 using Common.Logging; 5 6 namespace Quartz.Net.JobLibrary 7 { 8 public class JobTest1 : IJob 9 { 10 //使用Common.Logging.dll日志接口實現日志記錄 11 private static readonly ILog logger = 12 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 13 14 private static string URL = ConfigurationManager.AppSettings["URL"]; 15 16 #region IJob 成員 17 18 public void Execute(IJobExecutionContext context) 19 { 20 try 21 { 22 logger.Info("JobTest1 任務開始運行"); 23 //ShowMsgBox msgBox = new ShowMsgBox(); 24 //msgBox.ShowMsg(100, "AAAA", "計劃任務提醒"); 25 //WebServiceSoapClient client = new WebServiceSoapClient(new BasicHttpBinding(), new EndpointAddress(URL)); 26 //client.Shake(); 27 //ShowMsg(100, "AAAA", "計劃任務提醒"); 28 //Interop.ShowMessageBox("This a message from AlertService.", 29 // "AlertService Message"); 30 string path = System.Windows.Forms.Application.StartupPath; 31 Interop.CreateProcess("Quartz.Net.WinForm.exe", path + "\\"); 32 logger.Info("JobTest1 任務運行結束"); 33 } 34 catch (Exception ex) 35 { 36 logger.Error("JobTest1 運行異常", ex); 37 } 38 } 39 40 #endregion 41 42 } 43 } View Code2、在Quartz.Net.JobLibrary中,添加配置文件quartz_jobs.xml,配置信息如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 3 <!-- This file contains job definitions in schema version 2.0 format --> 4 <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"> 5 <processing-directives> 6 <overwrite-existing-data>true</overwrite-existing-data> 7 </processing-directives> 8 9 <schedule> 10 <!--定義JobTest1--> 11 <job> 12 <name>JobTest1</name> 13 <group>JobGroup</group> 14 <description>測試任務1</description> 15 <job-type>Quartz.Net.JobLibrary.JobTest1,Quartz.Net.JobLibrary</job-type> 16 <durable>true</durable> 17 <recover>false</recover> 18 </job> 19 20 <!--定義示JobTest2--> 21 <job> 22 <name>JobTest2</name> 23 <group>JobGroup</group> 24 <description>測試任務2</description> 25 <job-type>Quartz.Net.JobLibrary.JobTest2,Quartz.Net.JobLibrary</job-type> 26 <durable>true</durable> 27 <recover>false</recover> 28 </job> 29 30 <!--定義JobTest1 觸發器 每30秒執行一次任務--> 31 <trigger> 32 <cron> 33 <name>JobTestTrigger1</name> 34 <group>TriggerGroup</group> 35 <job-name>JobTest1</job-name> 36 <job-group>JobGroup</job-group> 37 <cron-expression>0/30 * * * * ?</cron-expression> 38 </cron> 39 </trigger> 40 41 <!--定義JobTest2 觸發器 每分鐘執行一次任務--> 42 <trigger> 43 <cron> 44 <name>JobTestTrigger2</name> 45 <group>TriggerGroup</group> 46 <job-name>JobTest2</job-name> 47 <job-group>JobGroup</job-group> 48 <cron-expression>0 * * * * ?</cron-expression> 49 </cron> 50 </trigger> 51 52 <!--定義JobTest2 觸發器 每天凌晨01:00執行一次任務--> 53 <trigger> 54 <cron> 55 <name>JobTestTrigger3</name> 56 <group>TriggerGroup</group> 57 <job-name>JobTest2</job-name> 58 <job-group>JobGroup</job-group> 59 <cron-expression>0 0 1 * * ?</cron-expression> 60 </cron> 61 </trigger> 62 </schedule> 63 64 <!-- Cron Expressions——Cron 表達式 65 Cron表達式被用來配置CronTrigger實例。Cron表達式是一個由7個子表達式組成的字符串。每個子表達式都描述了一個單獨的日程細節。這些子表達式用空格分隔,分別表示: 66 1. Seconds 秒 67 2. Minutes 分鐘 68 3. Hours 小時 69 4. Day-of-Month 月中的天 70 5. Month 月 71 6. Day-of-Week 周中的天 72 7. Year (optional field) 年(可選的域) 73 一個cron表達式的例子字符串為"0 0 12 ? * WED",這表示“每周三的中午12:00”。 74 單個子表達式可以包含范圍或者列表。例如:前面例子中的周中的天這個域(這裡是"WED")可以被替換為"MON-FRI", "MON, WED, FRI"或者甚至"MON-WED,SAT"。 75 通配符('*')可以被用來表示域中“每個”可能的值。因此在"Month"域中的*表示每個月,而在Day-Of-Week域中的*則表示“周中的每一天”。 76 所有的域中的值都有特定的合法范圍,這些值的合法范圍相當明顯,例如:秒和分域的合法值為0到59,小時的合法范圍是0到23,Day-of-Month中值得合法凡范圍是0到31, 77 但是需要注意不同的月份中的天數不同。月份的合法值是0到11。或者用字符串JAN,FEB MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV 及DEC來表示。 78 Days-of-Week可以用1到7來表示(1=星期日)或者用字符串SUN, MON, TUE, WED, THU, FRI 和SAT來表示. 79 '/'字符用來表示值的增量,例如, 如果分鐘域中放入'0/15',它表示“每隔15分鐘,從0開始”,如果在份中域中使用'3/20',則表示“小時中每隔20分鐘, 80 從第3分鐘開始”或者另外相同的形式就是'3,23,43'。 81 '?'字符可以用在day-of-month及day-of-week域中,它用來表示“沒有指定值”。這對於需要指定一個或者兩個域的值而不需要對其他域進行設置來說相當有用。 82 'L'字符可以在day-of-month及day-of-week中使用,這個字符是"last"的簡寫,但是在兩個域中的意義不同。例如,在day-of-month域中的"L"表示這個月的最後一天, 83 即,一月的31日,非閏年的二月的28日。如果它用在day-of-week中,則表示"7"或者"SAT"。但是如果在day-of-week域中,這個字符跟在別的值後面, 84 則表示"當月的最後的周XXX"。例如:"6L" 或者 "FRIL"都表示本月的最後一個周五。當使用'L'選項時,最重要的是不要指定列表或者值范圍,否則會導致混亂。 85 'W' 字符用來指定距離給定日最接近的周幾(在day-of-week域中指定)。例如:如果你為day-of-month域指定為"15W",則表示“距離月中15號最近的周幾”。 86 '#'表示表示月中的第幾個周幾。例如:day-of-week域中的"6#3" 或者 "FRI#3"表示“月中第三個周五”。 87 下面是一些表達式以及它們的含義。 88 Example Cron Expressions ——Cron表達式的例子 89 CronTrigger 90 例1 – 一個簡單的每隔5分鐘觸發一次的表達式 91 "0 0/5 * * * ?" CronTrigger 92 例2 – 在每分鐘的10秒後每隔5分鐘觸發一次的表達式(例如. 10:00:10 am, 10:05:10等.)。 93 "10 0/5 * * * ?" CronTrigger 94 例3 – 在每個周三和周五的10:30,11:30,12:30觸發的表達式。 95 "0 30 10-13 ? * WED,FRI" CronTrigger 96 例4 – 在每個月的5號,20號的8點和10點之間每隔半個小時觸發一次且不包括10點,只是8:30,9:00和9:30的表達式。 97 "0 0/30 8-9 5,20 * ?" 注意,對於單獨觸發器來說,有些日程需求可能過於復雜而不能用表達式表述,例如:9:00到10:00之間每隔5分鐘觸發一次, 98 下午1:00到10點每隔20分鐘觸發一次。這個解決方案就是創建兩個觸發器,兩個觸發器都運行相同的任務。 99 --> 100 </job-scheduling-data> View Code1、在解決方案中添加新項-選擇"windows 應用程序",我這裡命名為Quartz.Net.WinForm,具體實現從右下角逐漸顯示氣泡式窗口如圖所示:
點擊【確定】從右下角逐漸消失
通過JobTest1調用消息窗口,
string path = System.Windows.Forms.Application.StartupPath;
Interop.CreateProcess("Quartz.Net.WinForm.exe", path + "\\");
實現例如每一個小時檢測是否有計劃任務,一旦有任務,消息窗口彈出提醒!
1、cmd打開命令行工具:如果你系統是C盤,一般命令應該如下:
C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe "你的windows服務路徑"
反注冊如下:
C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /u "你的windows服務路徑"
當然也可以用sc命令
sc create windows服務名稱 binPath= "你的windows服務路徑"
刪除服務
sc delete windows服務名稱
這樣注冊就完了。
2、打開服務:
運行 下輸入services.msc打開服務管理。如圖:
刷新,可以查看到QuartzService.如下圖:
3、 啟動QuartzService,回到windows服務 應用程序所在目錄
以上四部分大體實現了類似一些新聞消息推送,下一篇講講具體程序打包、安裝等實現過程。希望大家多多推薦一下
處理消息隊列的順序。首先windows絕對不是按隊列先進先出的次序來處理的,而是有一定優先級的。優先級通過消息隊列的狀態標志來實現的。首先最高優先級的是別的線程發過來的消息(通過sendmessage),其次是處理登記消息隊列消息,再次處理QS_QUIT標志,再處理虛擬輸入隊列,再處理wm_paint最後是wm_timer
參考資料:windows消息機制 blog.csdn.net/...3.aspx
我們是小公司,十幾個人的。
我們公司使用的易 順 佳軟件不錯。
很好用啊,很容易上手,又有使用視頻。