好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

多线程、方便扩展的Windows服务程序框架

多线程、方便扩展的Windows服务程序框架

多线程、方便扩展的Windows服务程序框架

转载请注明出处:http://www.cnblogs.com/wu-jian/

前言

在项目应用中经常会碰到定时调度的工作,比如我曾经开发一个日访问量超过1000W的网站,如果这1000W访问都从数据库读取数据显示给用户,我的服务器肯定承受不了,于是我需要每10分钟把首页生成一次.html的静态文件;我的数据库里还有一张表,用来收集系统的各种异常、出错和危险信息,我需要把这张表里的记录每半个小时向运维人员发送一封邮件,这样他们就可以及时了解到系统运行情况;我需要每天凌晨统计数据生成报表以方便各个部门的头头们清早就可以查看;需要每月统计数据生成报表给BOSS以提醒他是不是该加薪了......诸如此类的需求越来越多,于是我考虑做一个公用的Windows服务程序框架,利用多线程,可同时执行多项任务,同时任务的扩展要简单快速,当然还需要可配置,每项任务的开关、执行时间等通过随时修改配置文件即可调控。

OK,本文主要针对Windows服务程序的实现以及多线程多任务的设计,代码中涉及到面向对象、反射、XML、Hashtable等基础技术,读者可自行查阅相关资料,不作深入探讨。个人能力有限,不足之处还请指正。程序于生产环境稳定运行,监控内存、IO等未发现异常。

设计

不论是生成静态页、发送邮件、还是统计数据生成报表,所有任务都可抽像出两个公共部分:配置与逻辑。配置包括任务项的名称描述,开启或关闭,程序集以及执行时间;逻辑包括任务执行与停止。所有任务需要继承和实现上述抽像类,另一个工具类实现IConfigurationSectionHandler接口,以添加自定义配置节点和对配置文件进行操作,工具类还包含一些公共静态方法,如下图所示:

编码

创建Windows服务项目

Visual Studio已为我们提供了Windows Service的模板,如下图所示:

配置类ServiceConfig

每项任务需要包含一个配置类并继承至Base.ServiceConfig。抽象属性必须在子类中实现,如果任务中包含更多的自定义配置,也可在此扩展。

之所以使用配置类,是希望把配置数据加载进内存,以避免Windows服务程序频繁对配置文件进行读取,减少IO性能消耗。

 using   System;

  namespace   WuJian.WindowsServiceDemo.Base
{
      ///   <summary> 
     ///   服务配置类
      ///   </summary> 
     public   abstract   class   ServiceConfig
    {
          #region  子类必需实现的抽象属性

         ///   <summary> 
         ///   工作项说明
          ///   </summary> 
         public   abstract   string   Description
        {
              get  ;
        }

          ///   <summary> 
         ///   工作项是否开启
          ///   </summary> 
         public   abstract   string   Enabled
        {
              get  ;
        }

          ///   <summary> 
         ///   工作项程序集
          ///   </summary> 
         public   abstract   string   Assembly
        {
              get  ;
        }

          ///   <summary> 
         ///   工作项执行间隔时间
          ///   </summary> 
         public   abstract   int   Interval
        {
              get  ;
        }

          #endregion 

         #region  扩展属性

         //  可扩展 

         #endregion  
    }
} 

工作项ServiceJob

每项任务必须包含一个工作类并继承至Base.ServiceJob。抽像方法Start()与Stop()必须在子类中实现。

 using   System;

  namespace   WuJian.WindowsServiceDemo.Base
{
      ///   <summary> 
     ///   工作项
      ///   </summary> 
     public   abstract   class   ServiceJob
    {
          //  配置对象 
         private   ServiceConfig mConfigObject;
          //  下次运行时间 
         private   DateTime mNextTime;
          //  任务是否在运行中 
         protected   bool   mIsRunning;

          ///   <summary> 
         ///   构造函数
          ///   </summary> 
         public   ServiceJob()
        {
              //  变量初始化 
             this .mNextTime =  DateTime.Now;
              this .mIsRunning =  false  ;
        }

          ///   <summary> 
         ///   配置对象
          ///   </summary> 
         public   ServiceConfig ConfigObject
        {
              get  {  return   this  .mConfigObject; }
              set  {  this .mConfigObject =  value; }
        }

          ///   <summary> 
         ///   开始工作
          ///   </summary> 
         public   void   StartJob()
        {
              if  ( this .mConfigObject !=  null  &&  this .mNextTime !=  null  )
            {
                  if  ( this .mConfigObject.Enabled.ToLower() ==  "  true  "  )
                {
                      if  (DateTime.Now >=  this  .mNextTime)
                    {
                          if  (! this  .mIsRunning)
                        {
                              this .mNextTime = DateTime.Now.AddSeconds(( double ) this  .mConfigObject.Interval);
                              this  .Start();
                        }
                    }
                }
            }
        }

          ///   <summary> 
         ///   停止工作
          ///   </summary> 
         public   void   StopJob()
        {
              this .mConfigObject =  null  ;
              this .mNextTime =  DateTime.Now;
              this .mIsRunning =  false  ;
              this  .Stop();
        }

          #region  子类必需实现的抽象成员

         ///   <summary> 
         ///   开始工作
          ///   </summary> 
         protected   abstract   void   Start();

          ///   <summary> 
         ///   停止工作
          ///   </summary> 
         protected   abstract   void   Stop();

          #endregion  
    }
} 


工具类ServiceTools

工具类实现了IConfigurationSectionHandler接口,封装了对app.config的读取、日志生成等静态方法。

 using   System;
  using   System.Collections.Specialized;
  using   System.Configuration;
  using   System.Xml;
  using   System.IO;

  namespace   WuJian.WindowsServiceDemo.Base
{
      ///   <summary> 
     ///   工具类
      ///   </summary> 
     public   class   ServiceTools : System.Configuration.IConfigurationSectionHandler
    {
          ///   <summary> 
         ///   获取AppSettings节点值
          ///   </summary> 
         ///   <param name="key"></param> 
         ///   <returns></returns> 
         public   static   string  GetAppSetting( string   key)
        {
              return   ConfigurationManager.AppSettings[key].ToString();
        }

          ///   <summary> 
         ///   获取configSections节点
          ///   </summary> 
         ///   <returns></returns> 
         public   static   XmlNode GetConfigSections()
        {
            XmlDocument doc  =  new   XmlDocument();
            doc.Load(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath);
              return   doc.DocumentElement.FirstChild;
        }

          ///   <summary> 
         ///   获取section节点
          ///   </summary> 
         ///   <param name="nodeName"></param> 
         ///   <returns></returns> 
         public   static  NameValueCollection GetSection( string   nodeName)
        {
              return   (NameValueCollection)ConfigurationManager.GetSection(nodeName);
        }

          ///   <summary> 
         ///   停止Windows服务
          ///   </summary> 
         ///   <param name="serviceName">  服务名称  </param> 
         public   static   void  WindowsServiceStop( string   serviceName)
        {
            System.ServiceProcess.ServiceController control  =  new   System.ServiceProcess.ServiceController(serviceName);
            control.Stop();
            control.Dispose();
        }

          ///   <summary> 
         ///   写日志
          ///   </summary> 
         ///   <param name="path">  日志文件  </param> 
         ///   <param name="cont">  日志内容  </param> 
         ///   <param name="isAppend">  是否追加方式  </param> 
         public   static   void  WriteLog( string  path,  string  cont,  bool   isAppend)
        {
              using  (StreamWriter sw =  new   StreamWriter(path, isAppend, System.Text.Encoding.UTF8))
            {
                sw.WriteLine(DateTime.Now);
                sw.WriteLine(cont);
                sw.WriteLine(  ""  );
                sw.Close();
            }
        }

          ///   <summary> 
         ///   实现接口以读写app.config
          ///   </summary> 
         ///   <param name="parent"></param> 
         ///   <param name="configContext"></param> 
         ///   <param name="section"></param> 
         ///   <returns></returns> 
         public   object  Create( object  parent,  object   configContext, System.Xml.XmlNode section)
        {
            System.Configuration.NameValueSectionHandler handler  =  new   System.Configuration.NameValueSectionHandler();
              return   handler.Create(parent, configContext, section);
        }

    }  //  end class 
}

框架代码

首先把所有任务都放进内存(Hashtable),这个过程使用了反射。然后使用托管的线程池执行多任务,如下代码所示:

 using   System;
  using   System.Collections.Generic;
  using   System.ComponentModel;
  using   System.Data;
  using   System.Diagnostics;
  using   System.ServiceProcess;
  using   System.Text;
  using   System.IO;
  using   System.Collections;
  using   System.Collections.Specialized;
  using   System.Xml;
  using   System.Reflection;
  using   System.Threading;

  namespace   WuJian.WindowsServiceDemo
{
      public   partial   class   Service1 : ServiceBase
    {
          //  用哈希表存放任务项 
         private   Hashtable hashJobs;

          public   Service1()
        {
            InitializeComponent();
        }

          protected   override   void  OnStart( string  [] args)
        {
              //  启动服务 
             this  .runJobs();
        }

          protected   override   void   OnStop()
        {
              //  停止服务 
             this  .stopJobs();
        }

          #region  自定义方法

         private   void   runJobs()
        {
              try  
            {
                  //  加载工作项 
                 if  ( this .hashJobs ==  null  )
                {
                    hashJobs  =  new   Hashtable();

                      //  获取configSections节点 
                    XmlNode configSections =  Base.ServiceTools.GetConfigSections();
                      foreach  (XmlNode section  in   configSections)
                    {
                          //  过滤注释节点(如section中还包含其它节点需过滤) 
                         if  (section.Name.ToLower() ==  "  section  "  )
                        {
                              //  创建每个节点的配置对象 
                             string  sectionName = section.Attributes[ "  name  "  ].Value.Trim();
                              string  sectionType = section.Attributes[ "  type  "  ].Value.Trim();

                              //  程序集名称 
                             string  assemblyName = sectionType.Split( '  ,  ' )[ 1  ];
                              //  完整类名 
                             string  classFullName = assemblyName +  "  .Jobs.  "  + sectionName +  "  .Config  "  ;

                              //  创建配置对象 
                            Base.ServiceConfig config =  (Base.ServiceConfig)Assembly.Load(assemblyName).CreateInstance(classFullName);
                              //  创建工作对象 
                            Base.ServiceJob job = (Base.ServiceJob)Assembly.Load(config.Assembly.Split( '  ,  ' )[ 1 ]).CreateInstance(config.Assembly.Split( '  ,  ' )[ 0  ]);
                            job.ConfigObject  =  config;

                              //  将工作对象加载进HashTable 
                             this  .hashJobs.Add(sectionName, job);
                        }
                    }
                }

                  //  执行工作项 
                 if  ( this .hashJobs.Keys.Count >  0  )
                {
                      foreach  (Base.ServiceJob job  in   hashJobs.Values)
                    {
                          //  插入一个新的请求到线程池 
                         if   (System.Threading.ThreadPool.QueueUserWorkItem(threadCallBack, job))
                        {
                              //  方法成功排入队列 
                         }
                          else  
                        {
                              //  失败 
                         }
                    }
                }
            }
              catch   (Exception error)
            {
                Base.ServiceTools.WriteLog(Base.ServiceTools.GetAppSetting(  "  LOG_PATH  " ) +  "  Error.txt  " , error.ToString(),  true  );
            }
        }

          private   void   stopJobs()
        {
              //  停止 
             if  ( this .hashJobs !=  null  )
            {
                  this  .hashJobs.Clear();
            }
        }

          ///   <summary> 
         ///   线程池回调方法
          ///   </summary> 
         ///   <param name="state"></param> 
         private   void   threadCallBack(Object state)
        {
              while  ( true  )
            {
                ((Base.ServiceJob)state).StartJob();
                  //  休眠1秒 
                Thread.Sleep( 1000  );
            }
        }

          #endregion  

    }  //  end class 
}

配置文件

示例中定义了两项任务Job1与Job2,为简单演示,这两项任务分别每隔5秒与10秒写一次文本文件。

首先在configSections中添加自定义节点,然后在自定义节点中配置任务的基本属性,为方便扩展,利用反射获取assembly属性来创建任务对象。

 <?  xml version="1.0"  ?> 
 <  configuration  > 

   <  configSections  > 
     <!--  自定义工作项,name属性请与Jobs下的任务目录同名,会据此加载该任务的config对象  --> 
     <  section   name  ="Job1"   type  ="WuJian.WindowsServiceDemo.Base.ServiceTools,WuJian.WindowsServiceDemo"  /> 
     <  section   name  ="Job2"   type  ="WuJian.WindowsServiceDemo.Base.ServiceTools,WuJian.WindowsServiceDemo"  /> 
   </  configSections  > 

   <  Job1  > 
     <  add   key  ="description"   value  ="任务一"   /> 
     <  add   key  ="enabled"   value  ="true"   /> 
     <  add   key  ="assembly"   value  ="WuJian.WindowsServiceDemo.Jobs.Job1.Job,WuJian.WindowsServiceDemo"   /> 
     <  add   key  ="interval"   value  ="5"   /> 
   </  Job1  > 

   <  Job2  > 
     <  add   key  ="description"   value  ="任务二"   /> 
     <  add   key  ="enabled"   value  ="true"   /> 
     <  add   key  ="assembly"   value  ="WuJian.WindowsServiceDemo.Jobs.Job2.Job,WuJian.WindowsServiceDemo"   /> 
     <  add   key  ="interval"   value  ="10"   /> 
   </  Job2  > 

   <  appSettings  > 
     <!--  日志路径  --> 
     <  add   key  ="LOG_PATH"   value  ="E:\Study\WindowsServiceDemo\Logs\"   /> 
   </  appSettings  > 

 </  configuration  > 

  

安装

在Service1设计模式点击鼠标右键,选择“添加安装程序”

设置服务的基本属性,包括执行权限,在WMI中的名称、备注、启动方式等。

最后执行installUtil命令在Windows中安装部署服务程序。为使用方便,编写了两个Bat文件以快速安装和卸载服务。

Install.bat用于安装服务

%systemroot%\microsoft.net\framework\v4.0.30319 \installUtil.exe  WuJian.WindowsServiceDemo .exe
pause 

UnInstall.bat用于卸载服务

%systemroot%\microsoft.net\framework\v4.0.30319\installUtil.exe  WuJian.WindowsServiceDemo .exe / u
pause 

执行Install.bat后就可以在Windows服务WMI中看到了,所下图所示:

执行结果

如下图所示,任务一生成job1.txt,每5秒记录一次时间;任务二生成job2.txt,每10秒记录一次时间。

DEMO下载

点击下载DEMO

 

分类:  C#

标签:  c# windows service ,  Windows 服务 ,  多线程服务 ,  反射 ,  线程池 ,  IConfigurationSectionHandler

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于多线程、方便扩展的Windows服务程序框架的详细内容...

  阅读:38次