• Quartz.NET 的任务调度管理工具


    [更新] 基于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}	{1}	{2}	{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.ServerJobs 目录下, 会报:

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

    解决办法有两种:

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

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

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

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

     View Code

    最后, 源码下载

    https://github.com/gruan01/QM

  • 相关阅读:
    CF1117G Recursive Queries
    P6604 [HNOI2016]序列 加强版
    高级图论
    P7708「Wdsr-2.7」八云蓝自动机 Ⅰ
    ISIJ2020 游记
    计算几何笔记 (模板)
    AC自动机学习笔记
    KMP学习笔记
    treap学习笔记
    HolyK学长的杂题选讲
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/4300937.html
Copyright © 2020-2023  润新知