现在的位置: 首页 > 综合 > 正文

分享CommunityServer(6) –JOb

2013年08月17日 ⁄ 综合 ⁄ 共 6211字 ⁄ 字号 评论关闭
 

      作为一个应用程序,我们有时候希望代码的执行不是完全由web用户的请求驱动的,希望可以在启动web进程时候,可以定时、周期性执行某些特定工作,这类工作我们一般希望是自动调度或者系统控制下的逻辑性调度。CS实现了相关的工作,设计了一个基础任务调度的框架,可以让我们达到在此框架下执行非Web用户请求驱动的任务执行。
任务的启动框架通常是在整个CS的启动时候,也就是通常我们说的global.asax中的:
protected void Application_Start(Object sender, EventArgs e)
         {
              Jobs.Instance().Start();
              EventLogs.Info("CS.Web Started", "Application", 200);
         }
通过Jobs的静态实例化函数Instance得到单例实例,而Jobs实例的Start函数就是启动框架的细节:
public void Start()
         {
              if (jobList.Count != 0)          //jobList表示当前正在执行的任务数量,如果存在多个任务,那就没必要再次启动这些Job
                   return;
              CSConfiguration csConfig = CSConfiguration.GetConfig();//获得配置
              if (!csConfig.IsBackgroundThreadingDisabled) //查看配置是否允许存在背景线程
              {//如果允许
                   XmlNode node = csConfig.GetConfigSection("CommunityServer/Jobs");//查看job的配置节
                   bool isSingleThread = true;
//以下来获取是否是单线程任务
                   XmlAttribute singleThreadAttribute = node.Attributes["singleThread"];
                   if (singleThreadAttribute != null && !Globals.IsNullorEmpty(singleThreadAttribute.Value) && string.Compare(singleThreadAttribute.Value, "false", true) == 0)
                        isSingleThread = false;
                   else
                   {
                        isSingleThread = true;
                        XmlAttribute minutes = node.Attributes["minutes"];
                       if (minutes != null && !Globals.IsNullorEmpty(minutes.Value))
                       {//转换设定周期从分到毫秒
                            try
                            {
                                 Interval = Int32.Parse(minutes.Value) * 60000;
                            }
                            catch
                            {
                                 Interval = 15 * 60000;
                            }
                       }
                   }
                   foreach (XmlNode jnode in node.ChildNodes)
                   {//读取每一个任务的配置信息,具体大家对照communityserver.config中的Jobs节
                       if (jnode.NodeType != XmlNodeType.Comment)
                       {
                            XmlAttribute typeAttribute = jnode.Attributes["type"];
                            XmlAttribute nameAttribute = jnode.Attributes["name"];
                            Type type = Type.GetType(typeAttribute.Value);
                            if (type != null)
                            {//如果任务名存在于jobList就不要重复启动了
                                 if (!jobList.Contains(nameAttribute.Value))
                                 {//后面会分析Job ,此处是按照类型启动一个人物,并且改任务的构造会读取配置节点信息jnode
                                     Job j = new Job(type, jnode);
                                      jobList[nameAttribute.Value] = j;
                                     if (!isSingleThread || !j.SingleThreaded)
                                          j.InitializeTimer();//初始化Timer 如果是多线程任务
                                 }
                            }
                       }
                   }
 
                   if (isSingleThread)    
                   {
                        //Create a new timer to iterate over each job 15分钟一次
                        singleTimer = new Timer(new TimerCallback(call_back), null, Interval, Interval);
                   }
              }
         }
从上面看到,如果任务是单线程的就通过Job实例的单一Timer 实例 singleTimer 来执行调度,如果是多线程的,就必须先每个Job自己准备一个timer来执行自己的任务调度。
对于共用一个Timer的任务调度,回调函数处理是这样的:
private void call_back(object state)
         {
              _isRunning = true;
              _started = DateTime.Now;
              singleTimer.Change(Timeout.Infinite, Timeout.Infinite);//设置timer的间隔无限长是为了初始化此Job的时候,禁止其他任务的调度,避免重入引起麻烦
              foreach (Job job in jobList.Values)
                   if (job.Enabled && job.SingleThreaded)   //调度joblist中的全部单调度任务
                        job.ExecuteJob();
              singleTimer.Change(Interval, Interval);         //下次执行调度,这个可以看到是15分钟,也就是说单线程调度的任务是15分钟
              _isRunning = false;
              _completed = DateTime.Now;
         }
单独执行的timr执行是这样的:
private void timer_Callback(object state)
        {
            if(!Enabled)
                return;
           _timer.Change( Timeout.Infinite, Timeout.Infinite );
_firstRun = -1;
ExecuteJob();
            if(Enabled)
                _timer.Change( Interval, Interval);
            else
                this.Dispose();
        }
可以看到最终任务调度无论是共用CS的缺省Timer还是使用单独的每个Job的timer,都是最终执行ExecuteJob(),在这个函数中,会具体执行每个Job的真正逻辑。
public void ExecuteJob()
        {
            OnPreJob();        //事件调度
            _isRunning = true;
            IJob ijob = this.CreateJobInstance(); //IJob接口
            if(ijob != null)
            {
                _lastStart = DateTime.Now;
                try
                {
                    ijob.Execute(this._node);     //真正的逻辑在此
                  _lastEnd = _lastSucess = DateTime.Now;                    
                }
                catch(Exception)
                {
                    this._enabled = !this.EnableShutDown;
                    _lastEnd = DateTime.Now;
                }
            }
            _isRunning = false;
            OnPostJob();      //事件调度
        }
通过每一个Job都要实现Ijob接口,而CS通过执行框架最终实例化Job,并且获得接口,然后调度Ijob的必要函数,从而实现了任务调度。
关于Jobs的配置信息,如下:
         <Jobsminutes="15"singleThread="true">
                  <jobname="SiteStatisticsUpdates"type="CommunityServer.Components.SiteStatisticsJob, CommunityServer.Components"enabled="true"enableShutDown="false"/>
               …….
                  <job singleThread="false"minutes="5"name="Emails"type="CommunityServer.Components.EmailJob, CommunityServer.Components"enabled="true"enableShutDown="false"failureInterval="1"numberOfTries="10"/>
               。。。。。
              <jobname="DeleteStaleSpamCommentsJob"type="CommunityServer.Blogs.Components.DeleteStaleSpamCommentsJob, CommunityServer.Blogs"enabled="true"enableShutDown="false"expirationDays="30"/>
          </Jobs>
如果任务是不需要单独使用timer的,那么就可以使用jobs的缺省设置,我们看到大多数任务是 singleThread 的,而 Emails 因为发送邮件的任务可能比较频繁,所以设定 singleThread 为false,这样就单独实现调度,时间间隔是5分钟。关于Job的个性化设定,可以通过job节的Attribute来补充,并且在当前Jobs的实现框架下,可以传递给具体的Job实例,从而实现任务的调度。
要在现有调度框架下增加任务调度,比较简单:
1、 实现一个调度任务具体类,并实现Ijob接口,具体需要调度执行的逻辑在Execute方法中实现。
2、 在CommunityServer.config中Jobs节下登记,并根据需要添加Attribute来传递设定信息。
 
如果要在自己的项目中也实现任务调度,还需要:
1、 实现一个Jobs类,参考CS的实现。
2、 在config文件中,加入Jobs配置节
3、 在Gobal.asax中实现任务的调度配置读取 和 调度初始化。
4、 增加自己的调度任务,实现Ijob接口。
 
这样,我们可以将同asp.net应用程序关系紧密,但是需要定时调度或者不需要用户http请求也必须触发的工作交给调度框架。这些应用可举例但不限于如下:
更新全局设定等,如全局人数统计等、分析、应用程序发生的重大事件记录等
对于一些缓存的操作,可一定时统一执行。如,我们通常有记录PV操作的记录,如果每次点击都要数据库记录,那么会严重消耗服务器资源,可以将这些操作缓存在全局变量,然后统一通过定制的Job来执行入库并复位计数器的操作。

还有一些需要耗时的操作,可以交给Job执行,分析后将结果展示给访问用户,避免在Http请求中等待执行,占用宝贵Http管道资源。 

抱歉!评论已关闭.