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】 --> 【程序打包】


项目解决方案,如图所示:

一、创建Windows服务


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配置文件,具体配置如下:

 <?xml version="1.0"?>
<configuration>
<configSections>
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
<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="INLINE"/>
</factoryAdapter>
</logging>
</common>
<log4net>
<appender name="InfoFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="log/" />
<appendToFile value="true" />
<param name="DatePattern" value="yyyyMMdd&quot;.txt&quot;" />
<rollingStyle value="Date" />
<maxSizeRollBackups value="" />
<maximumFileSize value="1024KB" />
<staticLogFileName value="false" />
<Encoding value="UTF-8" />
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="INFO" />
<param name="LevelMax" value="INFO" />
</filter>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="ErrorFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="log/error.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="" />
<maximumFileSize value="10240KB" />
<staticLogFileName value="true" />
<Encoding value="UTF-8" />
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="WARN" />
<param name="LevelMax" value="FATAL" />
</filter>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger - %message%newline" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="InfoFileAppender" />
<appender-ref ref="ErrorFileAppender" />
</root>
</log4net> <!--
We use quartz.config for this server, you can always use configuration section if you want to.
Configuration section has precedence here.
-->
<appSettings>
<!-- YYC.WebService URL地址 -->
<add key="URL" value="http://localhost:43093/WebService.asmx" />
</appSettings>
<!--
<quartz >
</quartz>
-->
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

二、创建Quartz.Net.JobLibrary并配置Job


1、在解决方案中添加新项-选择"类库",我这里命名为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 隔离,具体代码如下:

 using System;
using System.Runtime.InteropServices; namespace Quartz.Net.JobLibrary
{
public class Interop
{
public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
public static void ShowMessageBox(string message, string title)
{
int resp = ;
WTSSendMessage(
WTS_CURRENT_SERVER_HANDLE,
WTSGetActiveConsoleSessionId(),
title, title.Length,
message, message.Length,
, , out resp, false);
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int WTSGetActiveConsoleSessionId();
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSSendMessage(
IntPtr hServer,
int SessionId,
String pTitle,
int TitleLength,
String pMessage,
int MessageLength,
int Style,
int Timeout,
out int pResponse,
bool bWait); public static void CreateProcess(string app, string path)
{
IntPtr hToken;
IntPtr hDupedToken = IntPtr.Zero; PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa); STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si); int dwSessionID = WTSGetActiveConsoleSessionId();
bool result = WTSQueryUserToken(dwSessionID, out hToken); if (!result)
{
ShowMessageBox("WTSQueryUserToken failed", "AlertService Message");
} result = DuplicateTokenEx(
hToken,
GENERIC_ALL_ACCESS,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref hDupedToken
); if (!result)
{
ShowMessageBox("DuplicateTokenEx failed", "AlertService Message");
} IntPtr lpEnvironment;
result = CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false); if (!result)
{
ShowMessageBox("CreateEnvironmentBlock failed", "AlertService Message");
} result = CreateProcessAsUser(
hDupedToken,
path + app,
String.Empty,
ref sa, ref sa,
false, , IntPtr.Zero,
path, ref si, ref pi); if (!result)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
ShowMessageBox(message, "AlertService Message");
} if (pi.hProcess != IntPtr.Zero)
CloseHandle(pi.hProcess);
if (pi.hThread != IntPtr.Zero)
CloseHandle(pi.hThread);
if (hDupedToken != IntPtr.Zero)
CloseHandle(hDupedToken);
} [StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
} [StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public Int32 dwProcessID;
public Int32 dwThreadID;
} [StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public Int32 Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
} public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
} public enum TOKEN_TYPE
{
TokenPrimary = ,
TokenImpersonation
} public const int GENERIC_ALL_ACCESS = 0x10000000; [DllImport("kernel32.dll", SetLastError = true,
CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool CloseHandle(IntPtr handle); [DllImport("advapi32.dll", SetLastError = true,
CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandle,
Int32 dwCreationFlags,
IntPtr lpEnvrionment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
ref PROCESS_INFORMATION lpProcessInformation); [DllImport("advapi32.dll", SetLastError = true)]
public static extern bool DuplicateTokenEx(
IntPtr hExistingToken,
Int32 dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel,
Int32 dwTokenType,
ref IntPtr phNewToken); [DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSQueryUserToken(
Int32 sessionId,
out IntPtr Token); [DllImport("userenv.dll", SetLastError = true)]
static extern bool CreateEnvironmentBlock(
out IntPtr lpEnvironment,
IntPtr hToken,
bool bInherit);
}
}

Quartz.Net.JobLibrary类库中再分别添加两个"作业"类JobTest1.cs,JobTest2.cs:

代码如下

JobTest1.cs

 using System;
using System.Configuration;
using System.Reflection;
using Common.Logging; namespace Quartz.Net.JobLibrary
{
public class JobTest1 : IJob
{
//使用Common.Logging.dll日志接口实现日志记录
private static readonly ILog logger =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static string URL = ConfigurationManager.AppSettings["URL"]; #region IJob 成员 public void Execute(IJobExecutionContext context)
{
try
{
logger.Info("JobTest1 任务开始运行");
//ShowMsgBox msgBox = new ShowMsgBox();
//msgBox.ShowMsg(100, "AAAA", "计划任务提醒");
//WebServiceSoapClient client = new WebServiceSoapClient(new BasicHttpBinding(), new EndpointAddress(URL));
//client.Shake();
//ShowMsg(100, "AAAA", "计划任务提醒");
//Interop.ShowMessageBox("This a message from AlertService.",
// "AlertService Message");
string path = System.Windows.Forms.Application.StartupPath;
Interop.CreateProcess("Quartz.Net.WinForm.exe", path + "\\");
logger.Info("JobTest1 任务运行结束");
}
catch (Exception ex)
{
logger.Error("JobTest1 运行异常", ex);
}
} #endregion }
}

2、在Quartz.Net.JobLibrary中,添加配置文件quartz_jobs.xml,配置信息如下:

 <?xml version="1.0" encoding="UTF-8"?>

 <!-- This file contains job definitions in schema version 2.0 format -->
<job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
<processing-directives>
<overwrite-existing-data>true</overwrite-existing-data>
</processing-directives> <schedule>
<!--定义JobTest1-->
<job>
<name>JobTest1</name>
<group>JobGroup</group>
<description>测试任务1</description>
<job-type>Quartz.Net.JobLibrary.JobTest1,Quartz.Net.JobLibrary</job-type>
<durable>true</durable>
<recover>false</recover>
</job> <!--定义示JobTest2-->
<job>
<name>JobTest2</name>
<group>JobGroup</group>
<description>测试任务2</description>
<job-type>Quartz.Net.JobLibrary.JobTest2,Quartz.Net.JobLibrary</job-type>
<durable>true</durable>
<recover>false</recover>
</job> <!--定义JobTest1 触发器 每30秒执行一次任务-->
<trigger>
<cron>
<name>JobTestTrigger1</name>
<group>TriggerGroup</group>
<job-name>JobTest1</job-name>
<job-group>JobGroup</job-group>
<cron-expression>/ * * * * ?</cron-expression>
</cron>
</trigger> <!--定义JobTest2 触发器 每分钟执行一次任务-->
<trigger>
<cron>
<name>JobTestTrigger2</name>
<group>TriggerGroup</group>
<job-name>JobTest2</job-name>
<job-group>JobGroup</job-group>
<cron-expression> * * * * ?</cron-expression>
</cron>
</trigger> <!--定义JobTest2 触发器 每天凌晨01:00执行一次任务-->
<trigger>
<cron>
<name>JobTestTrigger3</name>
<group>TriggerGroup</group>
<job-name>JobTest2</job-name>
<job-group>JobGroup</job-group>
<cron-expression> * * ?</cron-expression>
</cron>
</trigger>
</schedule> <!-- Cron Expressions——Cron 表达式
Cron表达式被用来配置CronTrigger实例。Cron表达式是一个由7个子表达式组成的字符串。每个子表达式都描述了一个单独的日程细节。这些子表达式用空格分隔,分别表示:
. Seconds 秒
. Minutes 分钟
. Hours 小时
. Day-of-Month 月中的天
. Month 月
. Day-of-Week 周中的天
. Year (optional field) 年(可选的域)
一个cron表达式的例子字符串为"0 0 12 ? * WED",这表示“每周三的中午12:”。
单个子表达式可以包含范围或者列表。例如:前面例子中的周中的天这个域(这里是"WED")可以被替换为"MON-FRI", "MON, WED, FRI"或者甚至"MON-WED,SAT"。
通配符('*')可以被用来表示域中“每个”可能的值。因此在"Month"域中的*表示每个月,而在Day-Of-Week域中的*则表示“周中的每一天”。
所有的域中的值都有特定的合法范围,这些值的合法范围相当明显,例如:秒和分域的合法值为0到59,小时的合法范围是0到23,Day-of-Month中值得合法凡范围是0到31,
但是需要注意不同的月份中的天数不同。月份的合法值是0到11。或者用字符串JAN,FEB MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV 及DEC来表示。
Days-of-Week可以用1到7来表示(=星期日)或者用字符串SUN, MON, TUE, WED, THU, FRI 和SAT来表示.
'/'字符用来表示值的增量,例如, 如果分钟域中放入'0/15',它表示“每隔15分钟,从0开始”,如果在份中域中使用'3/20',则表示“小时中每隔20分钟,
从第3分钟开始”或者另外相同的形式就是'3,23,43'。
'?'字符可以用在day-of-month及day-of-week域中,它用来表示“没有指定值”。这对于需要指定一个或者两个域的值而不需要对其他域进行设置来说相当有用。
'L'字符可以在day-of-month及day-of-week中使用,这个字符是"last"的简写,但是在两个域中的意义不同。例如,在day-of-month域中的"L"表示这个月的最后一天,
即,一月的31日,非闰年的二月的28日。如果它用在day-of-week中,则表示""或者"SAT"。但是如果在day-of-week域中,这个字符跟在别的值后面,
则表示"当月的最后的周XXX"。例如:"6L" 或者 "FRIL"都表示本月的最后一个周五。当使用'L'选项时,最重要的是不要指定列表或者值范围,否则会导致混乱。
'W' 字符用来指定距离给定日最接近的周几(在day-of-week域中指定)。例如:如果你为day-of-month域指定为"15W",则表示“距离月中15号最近的周几”。
'#'表示表示月中的第几个周几。例如:day-of-week域中的"6#3" 或者 "FRI#3"表示“月中第三个周五”。
下面是一些表达式以及它们的含义。
Example Cron Expressions ——Cron表达式的例子
CronTrigger
例1 – 一个简单的每隔5分钟触发一次的表达式
"0 0/5 * * * ?" CronTrigger
例2 – 在每分钟的10秒后每隔5分钟触发一次的表达式(例如. :: am, ::10等.)。
"10 0/5 * * * ?" CronTrigger
例3 – 在每个周三和周五的10:,:,:30触发的表达式。
"0 30 10-13 ? * WED,FRI" CronTrigger
例4 – 在每个月的5号,20号的8点和10点之间每隔半个小时触发一次且不包括10点,只是8:,:00和9:30的表达式。
"0 0/30 8-9 5,20 * ?" 注意,对于单独触发器来说,有些日程需求可能过于复杂而不能用表达式表述,例如::00到10:00之间每隔5分钟触发一次,
下午1:00到10点每隔20分钟触发一次。这个解决方案就是创建两个触发器,两个触发器都运行相同的任务。
-->
</job-scheduling-data>

三、创建Quartz.Net.WinForm消息窗口


1、在解决方案中添加新项-选择"windows 应用程序",我这里命名为Quartz.Net.WinForm,具体实现从右下角逐渐显示气泡式窗口如图所示:

点击【确定】从右下角逐渐消失

通过JobTest1调用消息窗口,

string path = System.Windows.Forms.Application.StartupPath;
Interop.CreateProcess("Quartz.Net.WinForm.exe", path + "\\");

实现例如每一个小时检测是否有计划任务,一旦有任务,消息窗口弹出提醒!

四、注册Windows服务


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服务调用Quartz.net 实现消息调度的更多相关文章

  1. 使用普通Windows服务创建Quartz.Net服务项目

    Quartz.NET 项目地址 http://quartznet.sourceforge.net/ 源码下载地址:Quartz.Net.2.0 首先创建Quartz.Net.2.0解决方案,添加 Wi ...

  2. 基于.NetCore和ABP框架如何让Windows服务执行Quartz定时作业

    demo地址:ABP.WindowsService 该系列文章启发自 How to: Create a Windows Service that schedules jobs, logs and is ...

  3. Windows服务调用Office时,未将对象引用的实例

    Windows键+R键                         回车 输入:comexp.msc -32               回车 点击控制台根节点--组件服务--计算机--我的电脑- ...

  4. C#判断程序是由Windows服务启动还是用户启动

    在Windows系统做网络开发,很多时候都是使用Windows服务的模式,但在调度阶段,我们更多的是使用控制台的模式.在开发程序的时候,我们在Program的Main入口进行判断.最初开始使用Envi ...

  5. Windows服务使用log4net记录日志

    该文章是系列文章 基于.NetCore和ABP框架如何让Windows服务执行Quartz定时作业 的其中一篇. 比较流行的日志组件有以下四种,Topshelf都有相应的组件提供 log4net NL ...

  6. 通过SSIS监控远程服务器Windows服务并发送邮件报警!

    利用SSIS不仅可以做BI项目的ETL,而且还可以做一些系统监控和维护工作,由于之前供应商写的Windows服务是读取ESB的消息进行处理,且通过OA流程与访客系统进行了集成,无论是ESB出现状况,还 ...

  7. Windows服务使用Windsor容器

    该文章是系列文章 基于.NetCore和ABP框架如何让Windows服务执行Quartz定时作业 的其中一篇. Windsor是ABP框架自带的IOC容器. 关于什么是IOC,你可以Bing或者Go ...

  8. 使用 Topshelf 组件一步一步创建 Windows 服务 (2) 使用Quartz.net 调度

    上一篇说了如何使用 Topshelf 组件快速创建Windows服务,接下来介绍如何使用 Quartz.net 关于Quartz.net的好处,网上搜索都是一大把一大把的,我就不再多介绍. 先介绍需要 ...

  9. windows 服务实现定时任务调度(Quartz.Net)

    我们通常在一些情况下需要软件具有一个自动执行某些任务的功能,但是又不希望直接启动软件,或者每次都要手动的来启动软件,这时我们可可以考虑到windows服务了. 首先创建一个windows服务项目(详细 ...

随机推荐

  1. Android SlidingMenu侧滑菜单使用

    把下载的侧滑菜单压缩包打开,会有一个library文件夹,在eclipse中import existing android code into workspace,导入library文件夹,并且选择作 ...

  2. eclipse调试web项目

    Eclipse上的Web项目调试 在Eclipse中开发Web项目的首要难题就是如何进行代码调试.本文简要说明一下在Eclipse中使用Tomcat和Jetty调试Java Web项目的方法. Tom ...

  3. Haproxy安装及配置

    1.安装 # wget http://haproxy.1wt.eu/download/1.3/src/haproxy-1.3.20.tar.gz # tar zcvf haproxy-1.3.20.t ...

  4. C编译器剖析PDF文档及UCC编译器162.3

    http://blog.csdn.net/sheisc/article/details/42387857 http://blog.csdn.net/sheisc/article/details/455 ...

  5. MyBatis学习总结_15_定制Mybatis自动代码生成的maven插件

    ==================================================================================================== ...

  6. Xlib: connection to ":0.0" refused by server Xlib: No protocol specified解决方案

    Xlib: connection to ":0.0" refused by server Xlib:  No protocol specified 解决办法: 1. 退出oracl ...

  7. minhash

    minhash是一种基于jaccard index 相似度的算法.属于LSH(Location Sensitive Hash)家族中的一员. jaccard index :有两个集合A={a , b ...

  8. javascript 中文数字阿拉伯数字转换类 Nzh

    之前工作中碰到了数字转中文的情景,网上找的现成方法或多或少不合我的口味,最后还是自已写了一个. 现在整理了一下,补充了繁体,自定义字符,以及反函数(中文数字转阿拉伯数字) 现在发布出来,希望能合大家的 ...

  9. (5)XML转化成TXT

    using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Tex ...

  10. Javascript上下文

    var User = { count: 1, getCount: function() { return this.count; } }; console.log(User.getCount()); ...