Community Server專題七: Job & Timer

Community Server專題七: Job & Timer

CSHttpModule.cs文件中的Init方法下有這樣一行:

接着在Dispose方法中還有這麼一行:

Job?什麼是Job,在CS運行過程中有什麼用途,又是如何運行的?這篇專題將敘述Job的工作流程.

你可以這裏理解CS中的Job:“幹一些零碎事情的鐘點工”。

講解之前要先了解一個接口:IDisposableMSDN是這樣定義的:定義一種釋放分配的非託管資源的方法。當託管對象不再使用時,垃圾回收器會自動釋放分配給該對象的內存,不過,進行垃圾回收的時間不可預知。另外,垃圾回收器對窗口句柄、打開的文件和流等非託管資源一無所知。

將此接口的 Dispose 方法與垃圾回收器一起使用來顯式釋放非託管資源。當不再需要對象時,對象的使用者可以調用此方法。非託管資源(unmanaged resource)?大致可以這樣理解:類實例封裝的對不受運行庫管理的資源(窗口句柄、數據庫連接等),這些類實例都必須實現IDisposable接口,如:SqlCommand SqlCeConnectionTimer等。當一個類實現IDisposable時,實例的正確用法是當對象不在需要時調用Dispose方法刪除它,因此,在你實現一個類,而該類又包含其他實現IDisposable的類時,必須調用Dispose方法。這通常意味着在該類中你必須實現IDisposable

注:C#語言對Disposable有特殊的支持,你經常會看到如下一段代碼:

 

 

using(SqlConnection connection = new SqlConnection(connectionString))

{

    //
do something

}

這裏的using就是對IDisposable接口的支持來實現對象清除。

 

言歸正傳,下面打開CommunityServerComponents項目中Configuration文件夾中的Job.cs

 

 

public class Job : IDisposable

{

//代碼太長…

}

原來Job是實現了IDisposable的類,有了上面對IDisposable的解釋不難理解,由於Job中調用了Timer,Timer又是實現了IDisposable的類,Job類實現接口IDisposable是爲了釋放使用的Timer,即調用Timer中的Dispose()。看看以下的代碼可以證實這一點:

 

 

 

public void Dispose()

{

    
if(_timer!= null && !disposed)

    
{

        
lock(this)

        
{

            _timer.Dispose();

            _timer 
= null;

            disposed 
= true;

        }


    }


}

還有必要對Timer類做一些簡單的介紹:Timer是提供以指定的時間間隔執行方法的機制,說白了就是一個定時器。Timer可以使用 TimerCallback 委託指定希望 Timer時間到達時執行的方法。也就是說,如果定時器的時間到達了,將執行TimerCallback 委託指向的方法。

 

(注:創建計時器時,可以指定在第一次執行方法之前等待的時間量(截止時間)以及此後的執行期間等待的時間量(時間週期)。可以使用 Change 方法更改這些值或禁用計時器。)

我們看一下代碼:

 

 

private Timer _timer = null;

public void InitializeTimer(Guid id)

{

     
if(_timer == null && Enabled)

     
{

         _timer 
= new Timer(new TimerCallback(timer_Callback), id,Interval, Interval);

     }


}


 

private void timer_Callback(object state)

{

    Guid id 
= (Guid)state;

    
if(id != Jobs.Instance().CurrentID)

    
{

        
this.Dispose();

        
return;

    }


 

 

 

    
if(!Enabled)

        
return;

 

    _timer.Change( Timeout.Infinite, Timeout.Infinite );

 

    ExecuteJob();

 

    
if(Enabled)

        _timer.Change( Interval, Interval);

    
else

        
this.Dispose();

 

}

通過

_timer = new Timer(new TimerCallback(timer_Callback), id,Interval, Interval);

先實例化了一個定時器,並制定了該定時器觸發時候調用的方法是timer_Callback

(前一個Interval指:

後一個Interval指:

)

在回調方法timer_Callback只要調用的是ExecuteJob()方法,該方法通過調用Job類的Jobs類中傳遞過來得xml節點信息,使用反射實例化一個具有IJob接口的類(IJob接口要求實現它的類都具有Execute方法),並且執行類中的Execute方法。我們分析一個實現了IJob的類,打開Components文件夾下的EmailJob.cs,該類中只有一個方法,就是Execute,該方法又調用Emails.cs下的SendQueuedEmails方法,主要用途是發送數據庫隊列中的Email(這裏Email來源於用戶註冊時需要發送的註冊成功的信息等等,通常的做法是在用戶註冊完畢後馬上就發送Email,而CS採用的是將要發送的Email存入數據庫,在一定的間隔時間後統一處理這段時間內的所有Email發送,處理的類就是EmailJob.cs)。這裏還要注意一點:TimerCallback的實例不在創建計時器的線程中執行,而是在系統提供的一個單獨線程池線程中執行。

再來看看CommunityServerComponents項目下的Jobs.cs文件,這個類第一次看設計的有點蹩腳,它的構造函數是靜態的,但是在靜態的構造函數中實例化它自己,實例化後保存在static readonly的變量裏(暈了吧,嘿嘿!)其實這叫單件模式(Singleton模式),確保全局中有且只有一個Jobs實例。Jobs的實例主要完成從CS的配置文件中讀取出每個Job的配置信息,先看一下Job的配置信息:

<Jobs minutes = "5" singleThread = "false">

         
<job name = "SiteStatisticsUpdates" type = "CommunityServer.Components.SiteStatisticsJob, CommunityServer.Components" enabled = "true" enableShutDown = "false" />

         
<job name = "ForumsIndexing" type = "CommunityServer.Discussions.Components.ForumsSearchJob, CommunityServer.Discussions" enabled = "false" enableShutDown = "false" />

         
<job name = "WeblogIndexing" type = "CommunityServer.Blogs.Components.WeblogSearchJob, CommunityServer.Blogs" enabled = "false" enableShutDown = "false" />

         
<job name = "GalleryIndexing" type = "CommunityServer.Galleries.Components.GallerySearchJob, CommunityServer.Galleries" enabled = "false" enableShutDown = "false" />

         

         
<job name = "AnonymousUsers" minutes = "1" type = "CommunityServer.Components.AnonymousUserJob, 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" />

         
<job name = "Referrals" type = "CommunityServer.Components.ReferralsJob, CommunityServer.Components" enabled = "true" enableShutDown = "false" />

         
<job name = "Views" type = "CommunityServer.Components.ViewsJob, CommunityServer.Components" enabled = "true" enableShutDown = "false" />

         
<job name = "RecentBlogContent" type = "CommunityServer.Blogs.Components.RecentContentJob, CommunityServer.Blogs" enabled = "true" enableShutDown = "false" />

         
<job name = "RebuildThumbnailsJob" type = "CommunityServer.Galleries.Components.RebuildThumbnailsJob, CommunityServer.Galleries" picturesPerRun = "25" enabled = "true" enableShutDown = "false" />

</Jobs>

解釋一下這些xml的節點意思:

  

<Jobs minutes = "5" singleThread = "false">

 

minutes:執行回調函數TimerCallback的時間與時間間隔,這裏是5分鐘,也就是說執行回調函數在初始化請求之後的五分鐘開始,並且每五分鐘一次。

singleThread:是單線程還是多線程,前面說過爲Timer創建的TimerCallback的實例不在創建計時器的線程中執行,而是在系統提供的一個單獨線程池線程中執行,如果值爲“false”就會爲每個Job創建一個Timer線程,如果爲“true”就創建一個Timer

<job>節點比較靈活,一些屬性是該節點特有的,這是根據實現IJob接口類的需要決定的,以Email發送的類爲例:

:當頭CS版本中沒有用到。

<job singleThread = "false" minutes = "5" name = "Emails" type = "CommunityServer.Components.EmailJob, CommunityServer.Components" enabled = "true" enableShutDown = "false" failureInterval = "1" numberOfTries = "10" />

singleThread

Minutes:執行回調函數TimerCallback的時間與時間間隔,這裏是5分鐘。

name:該Job的唯一標識。

type:該Job所在的名字空間,逗號後面的是該Job所在的程序集dll文件名稱。

enabled:如果爲“false”該Job會被關閉,也就是不會實例化一個定時器。“true”則爲開啓該Job

enableShutDown:這個有點意思,用途是當該Job在執行Execute時,如果長生異常是否關閉這個Job,也就是說是否還允許它再次運行。“false”表示允許,“true”表示不允許。

failureInterval:如果發送郵件失敗,與下次嘗試再次發送的時間間隔,單位是分鐘。

numberOfTries:如果發送郵件失敗,該Job會嘗試幾次,這裏是10次。

   現在回到我們開始時候在CSHttpModule.cs中看到的這句:

Jobs.Instance().Start();

首先調用Jobs,實例化一個Jobs類,這個類在CS中有且只有一個實例。之後調用Jobs中的Start()方法。Jobs爲了確保系統的安全,在開始之前先調用Stop(),現釋放之前爲Job實例化的非託管資源(其實就是調用TimerJob下面的Dispose方法,這也是爲什麼Job要實現IDispose接口的原因,是想一下,如果前一次定義的定時器沒有被釋放,然後又接着實例化一個,然後這樣多重複幾次...哈哈,你的CS就會有N個線程在跑,服務器很快就會掛掉的)

釋放完資源後(無論有沒有CS都會這麼做),接着就會實例化每個實現了IJob的類,根據上述的配置文件定義一個Timer或者讓每個Job都定義一個自己的Timer(這就相當於給鐘點工統一一個工作時間或者給每個鐘點工都規定一個工作時間,規定完後該幹什麼的就幹去吧,只要時間到了就得幹活)。再往下,就自己分析吧,也就沒有什麼難題了

 

不知道爲什麼我的團隊中的幾個成員老是不能理解這個執行過程,我解釋了半天,最後發現了問題:

回到CSHttpModule.cs文件中來,這是一個實現了IHttpModule接口的類,實現該接口的類都要有一個Init方法,我們看到,所有的Job開始初始化的起點也是從這個方法中調用的。但是我的小組成員都認爲每個Http請求都會調用一次Init方法,

public void Init(HttpApplication application) 



    // Wire-up application events

    //

    application.BeginRequest += new EventHandler(this.Application_BeginRequest);

    application.AuthenticateRequest += new EventHandler(Application_AuthenticateRequest);

    application.Error += new EventHandler(this.Application_OnError);

    application.AuthorizeRequest += new EventHandler(this.Application_AuthorizeRequest);

    

    //settingsID = SiteSettingsManager.GetSiteSettings(application.Context).SettingsID;

    Jobs.Instance().Start();

    //CSException ex = new CSException(CSExceptionType.ApplicationStart, "Appication Started " +  AppDomain.CurrentDomain.FriendlyName);

    //ex.Log();

}

也就是說每個請求都實例化一個CSHttpModule,如果是這樣Init方法就會多次被調用,那麼如果有1000Http請求Jobs就會Start 1000次,這本來就分析不通。

其實認爲CSHttpModule會被實例化多次就是一個很大的錯誤,在整個CS運行過程中只會實例化一個CSHttpModule類,也就是說Init只會被執行一次。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章