[更新] 基于Quartz.NET 的任务调度管理工具

更新列表:

  1. 任务参数可视化.
  2. 立即中断正在执行的任务.
  3. 每个任务独立的应用程序域

上一版参见:

基于Quqrtz.NET 做的任务调度管理工具

界面具体变化如下:

任务参数可视化

如上图所示, 在管理任务的界面上就可以知道这个任务需哪些参数/类型 及 参数的说明.

实现方式, 在 Job 上添加 特性 :  ParameterTypeAttribute

 1 namespace JobA {
 2     [ParameterType(typeof(Parameter))]
 3     public class Job : IJob {
 4
 5         public static ILog Log = LogManager.GetLogger(typeof(Job));
 6         public void Execute(IJobExecutionContext context) {
 7             var dataMap = context.JobDetail.JobDataMap;
 8             //if (dataMap.ContainsKey("int")) {
 9             //    var pInt = dataMap.GetIntValue("int");
10             //    Console.WriteLine("1 JobA Parameter {0}", pInt);
11             //} else {
12             //    Log.Error("缺少参数 int, 未执行");
13             //    throw new JobExecutionException("缺少参数");
14             //}
15
16             var p = dataMap.Parse<Parameter>();
17             Console.WriteLine("{0}\t{1}\t{2}\t{3}", p.PDateTime, p.PDecimal, p.PInt, p.PNullableInt);
18
19
20             Thread.Sleep(TimeSpan.FromMinutes(3));
21         }
22     }
23 }

取参数直接调用 dataMap.Parse<Parameter>() 就行了.

Parse 方法在: QM.Common. DatamapParser 中定义.

相比原始的从 DataMap 中用 key / value 方法取参数, 这种处理方式的好处不言而喻.

但是也有缺点, DataMap 支持任何可序列化的类型,

而用这种方法只支持

string, decimal, long, int, single, double, DateTime, DateTimeOffset, TimeSpan , bool, char 这些类型. (没有做更深一步的处理, 有兴趣的,可以尝试自己去实现.)

每个任务独立的应用程序域

试想一下插件式开发, 如果你做的插件需要N个第三方DLL, 而这些DLL并没有引用到主项目上, 怎么办呢? 一堆的 FileLoadException, FileNotFoundException 等错误, 想想都头疼.

如果你开发的插件想拥有自己的配置文件, 又该怎么办呢? 自己实现一个配置文件读取解析? ini ? xml ? 头疼吧.

针对上面的问题, 在这里的最佳解决办法是 : 独立的应用程序域.

这个要从 IScheduler.JobFactory 说起.

在QM.Server.QMServer 的构造方法中, 指定 Schedule.JobFactory 为 IsolatedJobFactory

IsolatedJobFactory 的定义:

 1     public class IsolatedJobFactory : IJobFactory {
 2
 3         public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) {
 4             return NewJob(bundle.JobDetail.JobType);
 5         }
 6
 7         private IJob NewJob(Type jobType) {
 8             return new IsolatedJob(jobType);
 9         }
10
11         public void ReturnJob(IJob job) {
12             IDisposable disposable = job as IDisposable;
13             if (disposable != null) {
14                 disposable.Dispose();
15             }
16         }
17     }

从 NewJob 方法上可以看出, 实例出来的 Job 并不是最终要执行的 Job, 而是 IsolatedJob 的实例, 它类似中间人的身份.

IsolatedJob 实现了 IInterruptableJob 接口, 为中断执行中的任务埋下伏笔.

在 IsolatedJob 的构造方法中, 通过 IsolateDomainLoader 新建一个应用程序域:

IsolatedDomainLoader 的构造函数:

 1 public IsolateDomainLoader(string path, string configFileName = "") {
 2     AppDomainSetup setup = new AppDomainSetup();
 3     setup.ApplicationName = "IsolateDomainLoader";
 4     setup.ApplicationBase = path;
 5     setup.DynamicBase = path;
 6     setup.PrivateBinPath = path;
 7     setup.CachePath = setup.ApplicationBase;
 8     setup.ShadowCopyFiles = "true";
 9     setup.ShadowCopyDirectories = setup.ApplicationBase;
10     if (!string.IsNullOrWhiteSpace(configFileName)) {
11         setup.ConfigurationFile = configFileName;
12         setup.ConfigurationFile = Path.Combine(path, configFileName);
13     }
14     this.Domain = AppDomain.CreateDomain("ApplicationLoaderDomain", null, setup);
15 }

参数 path 即最终要执行的 job 所在的 dll 的路径.

configFileName 即独立的配置文件名称.

这样一来, 一个 job 一个文件夹, 文件夹内放置这个 job 相关的DLL和配置文件, 和主程序完全隔离开来.

上面说 IsolatedJob 是个中间人, 这里解释一下:

1, IsolatedJobFactory 的 NewJob 方法返回的是 IsolatedJob 的实例, 而不是最终要执行的 Job.

2, 在 IsolatedJob 中, 会通过独立的应用程序域 实例一个最终要执行的 Job 的远程对象(通过 RemoteObject).

3, 当中间人的 Execute 方法被调用时, 会调用远程 Job 对象的 Execute 方法.

4, Interrupt 方法同理.

远程对象续约

因为独立的应用程序域用到了远程对象: MarshalByRefObject, 因此涉及到了远程对象的租约过期及续租的问题.

远程对象的租约默认为 5 分钟, 可以重写 InitializeLifetimeService 方法来修改租约的有效期. 但是一个 Job 不确定要执行多长时间, 修改租约有效期不是很合适, 所以这里是通过续约的方式来处理租约过期的问题.

本人对租约了解不多, 不多嘴.感兴趣的话,可参见源码:

QM.RemoteLoader.RemoteObjectSponsor 类

和 QM.RemoteLoader.IsolateDomainLoader类的 GetObject 方法.

立即中断正在执行的任务

这个命题是有条件的, 即: 任务必须实现: IInterruptableJob 接口.

一般一个任务要执行很长时间, 如果不给个中断的接口, 那就只能关闭服务或等任务执行完毕了.

实现了这个接口,在配合 CancellationToken.ThrowIfCancellationRequested 方法就可以中断当前执行的任务了(别告诉我,你的任务是单线程的).

卸载域

任务执行完成后, 会将关联的 IsolatedJob对象释放, 在 IsolatedJob 的 Dispose 方法中,会把IsolateDomainLoader 对象释放,IsolateDomainLoader 释放的时候, 会把关联的子应用程序域卸载.
所以, 如果如果你的任务是多线程的, 请在线程远行完之前, 进行阻塞.

自定义Job的基类

目前, 如果自定义的 Job 的基类在第三方DLL中, 而且第三方DLL未引用到QM.Server 项目中, 并且不在 QM.Server\Jobs 目录下, 会报:

未能加载文件或程序集 XXX 或它的某一个依赖项。系统找不到指定的文件。

解决办法有两种:

1, 将缺少的DLL放到Jobs 目录下.

2, 将缺少的DLL添加引用到 QM.Server 中.

注意, 该限制只针对 Job 的基类. 除基类使用外的第三方DLL不需要这样做, 在JOB上引用就是了.

放上一段不用的, 可终止的 任务示例代码 给你做参考

  1     [ParameterType(typeof(FetcherParameter))]
  2     public class ScheduleFetcherJob : IInterruptableJob, IDisposable {
  3
  4
  5         private CancellationTokenSource CTS = new CancellationTokenSource();
  6
  7         private long JobID = DateTime.Now.Ticks;
  8         public void Execute(IJobExecutionContext context) {
  9
 10             var par = context.JobDetail.JobDataMap.Parse<FetcherParameter>();
 11             this.CTS.Token.Register(() => {
 12                 Console.WriteLine("正在尝试终止当前任务");
 13             });
 14             this.Execute(par);
 15         }
 16
 17         private string GetUrl(string org, string dest) {
 18             return string.Format("http://www.soushipping.com/shipping/{0}/{1}/{2}",
 19                 org, dest,
 20                 DateTime.Now.ToString("yyyy-MM-dd"));
 21         }
 22
 23         private void Execute(FetcherParameter par) {
 24             IFetcher<string> cityFetcher = new OrginCityFetcher();
 25             var orgCities = cityFetcher.GetDatasByUrl(cityFetcher.InitUrl);
 26             cityFetcher = new DestCityFetcher();
 27             var destCities = cityFetcher.GetDatasByUrl(cityFetcher.InitUrl);
 28
 29             Console.WriteLine("找到 {0} 条始发地, {1} 条目的地", orgCities.Count(), destCities.Count());
 30
 31             var limitScd = new LimitedConcurrencyLevelTaskScheduler(par.MaxThread);
 32
 33             var factory = new TaskFactory(limitScd);
 34
 35             List<Task> tasks = new List<Task>();
 36             foreach (var oc in orgCities.ToList()) {
 37                 foreach (var dc in destCities.ToList()) {
 38                     //注意下面这句的参数 t, 如果带这个参数,  IsCanceled 永远都为 false
 39                     //var task = Task.Factory.StartNew((t) => {
 40                     var task = factory.StartNew(() => {
 41                         this.CTS.Token.ThrowIfCancellationRequested();
 42
 43
 44                         var url = this.GetUrl(oc, dc);
 45
 46                         var fetcher = new ScheduleFetcher(url);
 47                         fetcher.PageFetchCompleted += fetcher_PageFetchCompleted;
 48                         fetcher.DownloadCompleted += fetcher_DownloadCompleted;
 49                         fetcher.Fetch();
 50                         fetcher = null;
 51                     }, this.CTS.Token)
 52                     .ContinueWith(t => {
 53                         //var completed = tasks.Where(tt => tt.Status == TaskStatus.RanToCompletion).Count();
 54                         //Console.WriteLine("{0}\t已完成:{1}", DateTime.Now.ToString("yyyy/MM/dd"), completed);
 55                         var arr = tasks.GroupBy(tt => tt.Status).Select(g => string.Format("{0}:{1}", g.Key, g.Count()));
 56                         Console.WriteLine("{0}\t{1}", DateTime.Now.ToString("MM/dd HH:mm:ss"), string.Join("  ", arr));
 57                         t.Dispose();
 58                     });//
 59                     //, TaskContinuationOptions.OnlyOnRanToCompletion)
 60                     //.ContinueWith(t => {
 61                     //    //Console.WriteLine("正在取消");
 62                     //    t.Dispose();
 63                     //}, TaskContinuationOptions.OnlyOnCanceled).ContinueWith(t => {
 64                     //    Console.WriteLine("发生错误");
 65                     //    t.Dispose();
 66                     //}, TaskContinuationOptions.OnlyOnFaulted);
 67
 68                     tasks.Add(task);
 69                 }
 70             }
 71
 72             try {
 73                 Task.WaitAll(tasks.ToArray());
 74             } catch (AggregateException ex) {
 75                 ex.Handle(er => er is TaskCanceledException);
 76             }
 77             Console.WriteLine("任务完成");
 78         }
 79
 80         #region
 81         //private void Execute2(FetcherParameter par) {
 82         //    IFetcher<string> cityFetcher = new OrginCityFetcher();
 83         //    var orgCities = cityFetcher.GetDatasByUrl(cityFetcher.InitUrl);
 84         //    cityFetcher = new DestCityFetcher();
 85         //    var destCities = cityFetcher.GetDatasByUrl(cityFetcher.InitUrl);
 86
 87         //    Console.WriteLine("找到 {0} 条始发地, {1} 条目的地", orgCities.Count(), destCities.Count());
 88
 89         //    var urls = orgCities.SelectMany(o => destCities.Select(d => this.GetUrl(o, d)));
 90
 91         //    var opts = new ParallelOptions() {
 92         //        MaxDegreeOfParallelism = par.MaxThread
 93         //    };
 94
 95         //    var total = urls.Count();
 96         //    object lockObj = new object();
 97
 98         //    //int sum = 0;
 99         //    Parallel.ForEach(urls, opts,
100         //        (url) => {
101         //            var fetcher = new ScheduleFetcher(url);
102         //            fetcher.PageFetchCompleted += fetcher_PageFetchCompleted;
103         //            fetcher.DownloadCompleted += fetcher_DownloadCompleted;
104         //            fetcher.Fetch();
105         //            fetcher = null;
106
107         //            lock (lockObj) {
108         //                total--;
109         //                Console.WriteLine(total);
110         //            }
111         //        }
112         //        );
113
114         //}
115         #endregion
116
117         private void fetcher_DownloadCompleted(object sender, DownloadArgs e) {
118             if (e.ExceptionStatus.HasValue) {
119                 Console.WriteLine("{0}\t请求地址: {1} 时,发生异常 {2}, 请检查网络环境.", DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), e.Url, e.ExceptionStatus);
120             }
121         }
122
123
124         private List<DIRTY_SCHEDULE> Datas = new List<DIRTY_SCHEDULE>();
125         private object lockObj = new object();
126
127         void fetcher_PageFetchCompleted(object sender, FetchArgs<DIRTY_SCHEDULE> e) {
128             var datas = e.Datas.Distinct(d => d.UNQTAG);
129
130             lock (lockObj) {
131                 this.Datas.AddRange(datas);
132                 if (this.Datas.Count > 100) {
133                     var tmp = new DIRTY_SCHEDULE[this.Datas.Count];
134                     this.Datas.CopyTo(tmp);
135                     this.Datas = new List<DIRTY_SCHEDULE>();
136                     //不是放入线程池, 而是立即执行的线程
137                     var tr = new Thread(new ParameterizedThreadStart(this.SaveDatas));
138                     tr.Start(tmp);
139                 }
140             }
141         }
142
143         private void SaveDatas(object state) {
144             IEnumerable<DIRTY_SCHEDULE> datas = (IEnumerable<DIRTY_SCHEDULE>)state;
145             var biz = new Biz.DirtyScheduleBiz();
146             biz.SaveDirtySchedule(datas, this.JobID);
147         }
148
149         public void Interrupt() {
150             this.CTS.Cancel();
151         }
152
153         ~ScheduleFetcherJob() {
154             Dispose(false);
155         }
156
157         public void Dispose() {
158             this.Dispose(true);
159             GC.SuppressFinalize(this);
160         }
161
162         protected virtual void Dispose(bool disposing) {
163             if (disposing) {
164                 if (this.CTS != null)
165                     this.CTS.Dispose();
166
167                 Console.WriteLine("Job Disposed");
168             }
169         }
170     }

最后, 源码下载

https://github.com/gruan01/QM

谢谢围观, 新年快乐!

----------------------------

题外: 大年初二, 我手一抖, 把断断续续写了快一年的东西给误删了!误删了啊!

用 360 的数据恢复功能, 没错, 是 360, 找出的文件, 我哭了, 数据库(SQLCE) 恢复出来的文件损坏, 用SQLCE的修复工具修复, 是个空库! 也就是说, 恢复出来的文件就是个屁!跟本就没有恢复出来!

EXCEL 文件也一样, 打不开!

更糟糕的是, 我没有验证, 恢复之后就直接盖到原来的位置上了!

泪奔啊, 大过年的, 我就忙着干这个去了!

时间: 2024-10-08 13:34:02

[更新] 基于Quartz.NET 的任务调度管理工具的相关文章

基于 Quartz 开发企业级任务调度应用--转

Quartz 基本概念及原理 Quartz Scheduler 开源框架 Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现.该项目于 2009 年被 Terracotta 收购,目前是 Terracotta 旗下的一个项目.读者可以到 http://www.quartz-scheduler.org/站点下载 Quartz 的发布版本及其源代码.笔者在产品开发中使用的是版本 1.8.4,因此本文内容基于该版本.本文不仅介绍如何应用 Quar

基于Quqrtz.NET 做的任务调度管理工具

国庆前,需求让我看了一下任务调度的数据表设计.和之前一样,有100多个字段,p1 ~ p100, 我说这是干嘛啊!按这写,写死去了! 然后在网上搜了一下开源的任务调度,第一个中意的就是 Quartz.NET,10.1 出来和老领导聚会,老领导说了另外一个东西:Zookeeper, 刚搜了一下,也有.NET的版本. 先入为主,Zookeeper 我就不深入了,整个10.1 在家基本除了看电影就是看 Quartz 了. Quartz.net 提供了 Remoting 方式,Remoting 我08年

基于操作系统原理的Webmin管理工具的安装使用

一.实验目的 1.了解Webmin管理工具的功能. 2.掌握Webmin的安装. 3.掌握Webmin管理工具的使用 二.实验内容 1.下载Webmin安装包. 2.在Linux主机中安装Webmin管理工具. 3.启动Webmin服务并登录到管理界面. 4.设置中文支持,并通过Webmin管理用户和硬件资源. 三.实验平台 1.实验工具: Vmware. 2. 实验设备: 已安装Red Hat Linux 5.0( 或红旗Linux. Slackware Linux) 的微型计算机. 四. 实

基于 Quartz 开发企业级任务调度应用

原文地址:https://www.ibm.com/developerworks/cn/opensource/os-cn-quartz/ Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现.作为一个优秀的开源调度框架,Quartz 具有功能强大,应用灵活,易于集成的特点.本文剖析了 Quartz 框架内部的基本实现原理,通过一些具体实例描述了应用 Quartz 开发应用程序的基本方法,并对企业应用中常见的问题及解决方案进行了讨论. Quart

quartz (一) 基于 Quartz 开发企业级任务调度应用

本文转自:http://www.ibm.com/developerworks/cn/opensource/os-cn-quartz/ Quartz 基本概念及原理 Quartz Scheduler 开源框架 Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现.该项目于 2009 年被 Terracotta 收购,目前是 Terracotta 旗下的一个项目.读者可以到 http://www.quartz-scheduler.org/站点下载

使用Quartz.NET进行任务调度管理

1.Quartz.NET 介绍 Quartz.NET是一个开源的作业调度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#写成,可用于winform和asp.net应用中.它提供了巨大的灵活性而不牺牲简单性.你能够用它来为执行一个作业而创建简单的或复杂的调度.它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等.        你曾经需要应用执行一个任务吗?这个任务每天或每周星期二晚上11:30,或许仅仅每个月的最后一天执行.一个自动执行而无

搭建一个基于CentOS的可视化zookeeper管理工具zkUI实现对zk的可视化管理

一. zookeeper 可视化工具 JMX => CLRProfile ZKUI => java写的一个可视化的web网站 github中下载 https://github.com/DeemOpen/zkui git 拉取地址 :https://github.com/DeemOpen/zkui.git CoentOS 安装 git maven 1. git 安装: (在linux下安装卸载git非常方便)  命令 :yum remove git 删除git  命令 :yum -y insta

最佳的MongoDB客户端管理工具

<最佳的MongoDB客户端管理工具> 作者:chszs,未经博主允许不得转载.经许可的转载需注明作者和博客主页:http://blog.csdn.net/chszs 一个好的MongoDB客户端管理工具,可以大大提高MongoDB应用的开发效率.MongoDB自带的Shell是一个很好的工具,但是它在操纵大数据集时就没那么直观了.因此使用MongoDB客户端GUI管理工具就非常必要了,下面推荐四个主要的工具. 一.MongoVUE 主页: http://www.mongovue.com/ M

BetterSnapTool for Mac(窗口管理工具)

BetterSnapTool for Mac特别版能自定义设置窗口大小,让您轻松管理窗口位置和大小,方法是将它们拖动到屏幕的一个角落或屏幕的顶部,左侧或右侧.这使您可以轻松地最大化窗口,并排放置它们,甚至可以将它们调整到屏幕的四分之一.此外,为了调整你想要的方式,你可以设置键盘快捷键. bettersnaptool允许您通过将窗口位置和大小拖动到屏幕的某个角落或屏幕的顶部.左侧或右侧来轻松管理窗口位置和大小.这使您可以方便地最大化窗口,并排放置,甚至将它们调整到屏幕的四分之一. 如果您需要的不仅