• 自动执行任务功能实现


    在项目开发过程中,由于业务需求,我们需要系统定时自动执行一些业务操作,如每天生产结束时需要自动汇总统计当天的生产情况,记录各个部门的库存情况等,为此,我编写了一个windows服务程序实现了这个功能,因为这个功能只是实现自动定时执行工作任务,具体工作任务的实现代码封装在不同的dll中,就是说该程序和具体业务逻辑是非耦合的,通用性比较强,所以我把它共享出来,希望能为大家以后遇到类似需求时提供一些参考思路。

     自动任务类的设计
    自动任务类
        /// <summary>
        /// 自动任务
        /// </summary>
        public class AutoTask
        {
            /// <summary>
            /// 任务名
            /// </summary>
            public string Name { get; set; }

            /// <summary>
            /// 执行周期, 1表示月,2表示周,3表示天,4表示小时,5表示秒
            /// </summary>
            public int Cycle { get; set; }

            /// <summary>
        /// 执行频率,当周期为小时时启用,如2表示30隔分钟执行1次,
    0.5表示2小时执行1次
            /// </summary>
            public double Frequency { get; set; }

            /// <summary>
            /// 周期中第几天执行,周期为月或者周时候启用
            /// </summary>
            public int Exec_Date { get; set; }

            /// <summary>
      /// 执行时间范围开始时间,周期为月或者周或者天的时候启用,
    如 6:30
            /// </summary>
            public string RangeStart { get; set; }

            /// <summary>
            /// 执行时间范围结束时间,周期为月或者周或者天的时候启用,
    如 7:00
            /// </summary>
            public string RangedEnd { get; set; }

            /// <summary>
            /// 上一次执行时间
            /// </summary>
            public DateTime? LastExecTime { get; set; }

            /// <summary>
            /// 执行方法: namespace.class.method
            /// </summary>
            public MethodInfo ExecMethod { get; set; }

            /// <summary>
            /// 状态: 1起用,0 停用
            /// </summary>
            public int Status { get; set; }

            /// <summary>
            /// 描述
            /// </summary>
            public string Description { get; set; }

        }
     
    执行方法信息类
       
    /// <summary>
        /// 自动任务中的执行代码信息
        /// </summary>
        public class MethodInfo
        {
            /// <summary>
            /// 所在程序集名称
            /// </summary>
            public string AssemblyName { get; set; }
        
            /// <summary>
            /// 类名
            /// </summary>
            public string ClassName { get; set; }
           
            /// <summary>
            /// 方法名称
            /// </summary>
            public string MethodName { get; set; }
           
            /// <summary>
            /// 参数集合
            /// </summary>
            public object[] Args { get; set; }
    }
    把我们的自动任务配置到xml文件里。(这里就配置两个作为示例)
    AutoTask.xml
    <?xml version="1.0"?>
    <ArrayOfAutoTask xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <AutoTask>
        <Name>WriteInvDayReport</Name>
        <Cycle>3</Cycle>  
        <RangeStart>2</RangeStart>
        <RangedEnd>3</RangedEnd>
        <LastExecTime>2010-07-23T15:14:10.375+08:00</LastExecTime>
        <ExecMethod>
          <AssemblyName>BCMES.Business, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</AssemblyName>
          <ClassName>BCMES.Business.SchemeService</ClassName>
          <MethodName>WriteInvDayReport</MethodName>     
        </ExecMethod>
        <Status>1</Status>
      </AutoTask>
     <AutoTask>
        <Name>RefreshSessions</Name>
        <Cycle>4</Cycle>
        <Frequency>15</Frequency>
        <Exec_Date>0</Exec_Date>
        <LastExecTime>2010-12-15T20:09:51</LastExecTime>
        <ExecMethod>
          <AssemblyName>BCMES.Business, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</AssemblyName>
          <ClassName>BCMES.Business.FactAPIHelper</ClassName>
          <MethodName>RefreshSession</MethodName>
        </ExecMethod>
        <Status>1</Status>
      </AutoTask>
      <AutoTask>
    </ArrayOfAutoTask>

    总体设计思路:创建一个widows服务程序,以某一频率读取autotask.xml文件,
    (读取前设置文件为只读),遍历文件上的所有任务,通过任务的最后执行时间与当前系统时间的比较,同时结合任务定义的执行周期和频率,判断出当前任务是否需要执行,不需执行,跳到下个任务,需要执行,则读取该任务的执行方法信息(程序集,类名,方法名),通过反射调用执行方法,执行完毕以后,修改该任务的最后执行时间为当前时间,遍历完成以后,以覆盖形式把任务重新写回xml文件,并去掉文件只读属性。大致过程如下:
    新建windows服务程序,在服务类文件Service1.cs放置一时钟控件 timer,设置时钟触发周期和触发事件代码
    Timer timer = new Timer();
    public static AutoTask_Exec autoTask = new AutoTask_Exec();
    protected override void OnStart(string[] args)
    {
    timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
    timer.Interval = (int)callTaskInterval; //此值可改为从配置文件读取
    }

       private void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
                autoTask.Run("AutoTask.xml");
    }

     AutoTask_Exec类代码如下

    public class AutoTask_Exec
        {

            /// <summary>
    /// 对象容器,对于执行频率较高的任务,将其执行方法的实例对象存到该容器里,避免重复新建对象
            /// </summary>
            private Dictionary<string, object> _objectContainer;
            public Dictionary<string, object> ObjectContainer
            {
                get
                {
                    if (_objectContainer == null)
                        _objectContainer = new Dictionary<string, object>();
                    return _objectContainer;
                }
                set { _objectContainer = value; }
            }

            public AutoTask CurExecTask { get; set; }

            public void Run(string fileName)
            {
                string xmlFileName = AppDomain.CurrentDomain.BaseDirectory + fileName;
                if (!File.Exists(xmlFileName) || File.GetAttributes(xmlFileName) == FileAttributes.ReadOnly)
                    return;

                File.SetAttributes(xmlFileName, FileAttributes.ReadOnly);
              
                int invokeCount = 0;
                List<AutoTask> tasks = new List<AutoTask>();
                try
                {
                    tasks = XmlSerializerExt.Desrialize<List<AutoTask>>(xmlFileName);
                }
                catch(Exception ex)
                {
                    WriteLog("autotask", "desrialize failure:" + ex.Message, EventLogEntryType.Error);
                    System.IO.File.SetAttributes(xmlFileName, FileAttributes.Normal);
                    return;
                }
              
                if (tasks == null || tasks.Count==0)
                {
                    System.IO.File.SetAttributes(xmlFileName, FileAttributes.Normal);
                    return;
                }

                foreach (AutoTask task in tasks)
                {
                    if (task.Status == 1 && IsExecTime(task))
                    {

                        invokeCount++;
                        object target = null;
                        string timeStr = DateTime.Now.ToString();                  
                        string taskInfo = string.Format("autotask[{0}]", task.Name);
                        try
                        {
                            if (task.Cycle > 3 && ObjectContainer.ContainsKey(task.ExecMethod.ClassName))
                                target = ObjectContainer[task.ExecMethod.ClassName];
                            else
                            {
                                target = Activator.CreateInstance(task.ExecMethod.AssemblyName, task.ExecMethod.ClassName).Unwrap();
                                if (task.Cycle > 3)
                                    ObjectContainer.Add(task.ExecMethod.ClassName, target);
                            }
                            CurExecTask = task;
                            target.GetType().InvokeMember(task.ExecMethod.MethodName, BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, target, task.ExecMethod.Args);
                            CurExecTask = null;
                            if (task.Cycle < 4)
                            {
                                WriteLog(taskInfo, "success", EventLogEntryType.Information);
                            }
                        }
                        catch (Exception ex)
                        {
                            WriteLog(taskInfo, "execute autotask failure:" + ex.Message, EventLogEntryType.Error);
                        }
                        finally
                        {
                            task.LastExecTime = DateTime.Parse(timeStr);
                        }

                    }              
                }

                System.IO.File.SetAttributes(xmlFileName, FileAttributes.Normal);
                if (invokeCount > 0)
                {            
                    XmlSerializerExt.Serialize(tasks, xmlFileName);
                }

            }

            /// <summary>
            /// 判断当前任务是否到了执行时间
            /// </summary>
            /// <returns></returns>
            private bool IsExecTime(AutoTask task)
            {
                DateTime now = DateTime.Now;
                bool result = false;
                switch (task.Cycle)
                {
                    case 1:
                        result =
                            now.Day == task.Exec_Date
                            //是否指定了开始时间
                        && (string.IsNullOrEmpty(task.RangeStart) || (now >= DateTime.Parse(task.RangeStart) && now <= DateTime.Parse(task.RangedEnd)))
                            //距离上次执行时间是否隔了一个月
                        && (!task.LastExecTime.HasValue || (task.LastExecTime.HasValue && now > task.LastExecTime.Value && now.Month != task.LastExecTime.Value.Month));
                        break;
                    case 2:
                        result =
                            (int)DateTime.Now.DayOfWeek == task.Exec_Date
                         && (string.IsNullOrEmpty(task.RangeStart) || (now >= DateTime.Parse(task.RangeStart) && now <= DateTime.Parse(task.RangedEnd)))
                            //距离上次执行时间是否隔了一个星期
                         && (!task.LastExecTime.HasValue || (task.LastExecTime.HasValue && now.Date > task.LastExecTime.Value.Date ));
                        break;
                    case 3:
                        result =
                           (string.IsNullOrEmpty(task.RangeStart) || (now >= DateTime.Parse(task.RangeStart) && now <= DateTime.Parse(task.RangedEnd)))
                            //距离上次执行时间是否隔了一天
                         && (!task.LastExecTime.HasValue || (task.LastExecTime.HasValue && now.Date > task.LastExecTime.Value.Date));
                        break;
                    case 4:
                        if (task.LastExecTime.HasValue)
                        {
                            DateTime comp1 = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0);
                            DateTime comp2 = new DateTime(task.LastExecTime.Value.Year, task.LastExecTime.Value.Month, task.LastExecTime.Value.Day, task.LastExecTime.Value.Hour, task.LastExecTime.Value.Minute, 0).AddMinutes(60 / task.Frequency);
                            result = comp1 >= comp2;
                        }
                        else
                            result = true;                  
                        break;
                    case 5:
                        result = true;
                        break;
                    default:
                        result = false;
                        break;
                }
                return result;
            }

            private void WriteLog(string source, string msg, EventLogEntryType eventType)
            {
                EventLog myLog = new EventLog();
                myLog.Source = source;
                myLog.WriteEntry(msg, eventType);
            }
        }


    里面引用的序列化功能类代码

    /// <summary>
        /// xml序列化扩展类
        /// </summary>
        public class XmlSerializerExt
        {
            /// <summary>
            /// 把对象序列化到xml文件
            /// </summary>
            /// <param name="obj"></param>   
            public static void Serialize(object obj, string filePath)
            {
                XmlSerializer xs = new XmlSerializer(obj.GetType());
                using (MemoryStream ms = new MemoryStream())
                {
                    System.Xml.XmlTextWriter xtw = new System.Xml.XmlTextWriter(ms, System.Text.Encoding.UTF8);
                    xtw.Formatting = System.Xml.Formatting.Indented;
                    xs.Serialize(xtw, obj);
                    using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                    {
                        xs.Serialize(fs, obj);
                        fs.Close();
                    }

                }
            }

            /// <summary>
            /// 反序列化方法
            /// </summary>
            /// <typeparam name="T">反序列化对象类型</typeparam>
            /// <param name="xml">反序列化字符串或者xml文件路径</param>
            /// <returns></returns>
            public static T Desrialize<T>(string xml)
            {
                T obj = default(T);
                XmlSerializer xs = new XmlSerializer(typeof(T));
                TextReader tr;

                if (!File.Exists(xml))
                {
                    tr = new StringReader(xml);
                }
                else
                {
                    tr = new StreamReader(xml);
                }
                using (tr)
                {
                    obj = (T)xs.Deserialize(tr);
                }
                return obj;
            }
    }

    如何使用。至此,程序主体代码已经完成,至于如何安装windos服务,若没写过的同学请自行goolgle一下吧,我这里不再累述了。在xml文件上写上自己要自动执行的任务信息,同时把任务执行相关的dll和配置文件拷贝至和服务可执行文件同一目录下,启动服务,就ok了。 在项目实际应用中,我发现有个不足的地方是 由于任务是串行执行的,当其中一个任务的方法调用的执行时间比较长的时候(如处理数据量大,或者代码本身的原因),就会影响后续任务的执行,所以,我在第二版程序中做了改进,将一个xml文件扩充为可任意配置n个xml文件,每个文件单独使用一个时钟控件控制读取频率,这样,每个xml文件不但可以设置不同的读取频率,而且在执行过程中就算”卡”在某一任务上,也仅仅影响该xml文件上的后续任务,不会影响其他的xml文件上的任务执行。再解释一下主体代码处2个地方:
    Xml文件读取前后要修改文件属性的原因是为了识别当前xml文件处于运行状态还是非运行状态,如果处于运行状态,则表明前一个时间触发事件还没处理完毕,这时不应该读取xml文件。
    时钟控件的时间间隔设置应大于或等于xml文件里的自动任务的执行频率的最大值,假设有个自动任务每一分钟执行一次的话,那么系统至少每分钟就要读取xml文件一次,这容易理解吧。

  • 相关阅读:
    把eclipse 3.4的插件移动到独立目录中
    Crest大家都来山寨一个GObject吧
    c# 扩展方法奇思妙用性能篇一:扩展方法性能初测
    [幽默]今天看了几页c语言入门,想写个ERP, 帮我看看 技术上还差些什么?
    c# 扩展方法奇思妙用高级篇一:改进 Scottgu 的 "In" 扩展
    [个人]我的积分与排名日志
    反驳 老赵 之 “伪”递归
    c# 扩展方法奇思妙用变态篇一:由 Fibonacci 数列引出 “委托扩展” 及 “递推递归委托”
    c# 扩展方法奇思妙用高级篇五:ToString(string format) 扩展
    瑞士军刀 VS 单一职责原则
  • 原文地址:https://www.cnblogs.com/lindping/p/2004835.html
Copyright © 2020-2023  润新知