自动执行任务功能实现

在项目开发过程中,由于业务需求,我们需要系统定时自动执行一些业务操作,如每天生产结束时需要自动汇总统计当天的生产情况,记录各个部门的库存情况等,为此,我编写了一个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文件一次,这容易理解吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值