LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

使用ASP.NET實現Windows Service定時執行任務

admin
2012年1月23日 0:57 本文熱度 3650
我們怎樣才能在服務器上使用asp.net定時執行任務而不需要安裝windows service?我們經常需要運行一些維護性的任務或者像發送提醒郵件給用戶這樣的定時任務。這些僅僅通過使用Windows Service就可以完成。Asp.net通常是一個無狀態的提供程序,不支持持續運行代碼或者定時執行某段代碼。所以,我們不得不構建自己的windows service來運行那些定時任務。但是在一個共享的托管環境下,我們并不總是有機會部署我們自己的windwos service到我們托管服務提供商的web服務器上。我們要么買一個專用的服務器,當然這是非常昂貴的,要么就犧牲我們網站的一些功能。然而,運行一個定期執行的任務是一個非常有用的功能,特別是對那些需要發送提醒郵件的用戶、需要維護報表以及運行清理操作的的管理員而言。我將給你展示一種無須使用任何windows service,僅僅采用asp.net來運行定期任務的方式。


它怎樣工作


首先,我們需要asp.net中的某些“場景”,能夠持續不斷地運行并且給我們一個回調。而IIS上的web服務器就是一個很不錯的選擇。所以,我們需要從它那里很“頻繁”地獲得回調,這樣我們可以查看一個任務隊列,并且能夠看到是否有任務需要執行。現在,這里有一些方式可以為我們獲得對web服務器的“操作權”:


(1) 當一個頁面被請求


(2) 當一個應用程序被啟動


(3) 當一個應用程序被停止


(4) 當一個會話開啟、結束或者超時


(5) 當一個緩存項失效


一個頁面被請求是隨機的。如果幾個小時內沒有人訪問你的站點,那么幾個小時內你都無法完成任何“任務”。另外,一個請求的執行時間是非常短的,并且它本身也需要越快越好。如果你計劃在頁面請求的時候執行“計劃任務”,這樣頁面將會被迫執行很長時間,這將導致一個很糟糕的用戶體驗。所以,選擇在頁面請求的時機做這樣的操作不是一個好的選擇。


一個頁面被請求是隨機的。如果幾個小時內沒有人訪問你的站點,那么幾個小時內你都無法完成任何“任務”。另外,一個請求的執行時間是非常短的,并且它本身也需要越快越好。如果你計劃在頁面請求的時候執行“計劃任務”,這樣頁面將會被迫執行很長時間,這將導致一個很糟糕的用戶體驗。所以,選擇在頁面請求的時機做這樣的操作不是一個好的選擇。


當一個應該程序啟動時,Global.asax內的Application_Start方法給我們提供了一個回調。所以這是一個開啟后臺線程的好地方,后臺線程可以永久運行以執行“計劃任務”。然而,當該線程在web服務器由于零負載而“休息”一會兒的時候,卻可能被隨時“殺死”。


當一個應用程序停止的時候,我們同樣可以從Application_End方法獲得一個回調。但是我們在這里卻不能做任何事情,因為整個應該程序都已經快要結束運行了。Global.asax里的Session_Start會在當一個用戶訪問一個需要被實例化為新會話的頁面時被觸發。所以這也是一個隨機事件。而我們需要一個能持久且定期運行的“場景”。

一個緩存項的失效可以提供一個時間點或持續時間。在asp.net中你可以在Cache對象中增加一個實體,并且可以設置一個絕對失效時間,或者設置當其被從緩存中移除后失效。你可以利用下面的Cache類中的方法來做這些:





  1. public void Insert ( System.String key , System.Object value ,

  2. System.Web.Caching.CacheDependency dependencies ,

  3. System.DateTime absoluteExpiration ,

  4. System.TimeSpan slidingExpiration ,

  5. System.Web.Caching.CacheItemPriority priority ,

  6. System.Web.Caching.CacheItemRemovedCallback onRemoveCallback )


onRemoveCallback是一個方法的委托,該方法在一個緩存項失效時被調用。在該方法中,我們可以做任何我們想做的事情。所以,這是一個定期、持續運行代碼而不需要任何頁面請求的很好的候選。



這意味著,我們可以在一個緩存項失效時模擬一個簡單的windows service。


創建緩存項的回調


首先,在Application_Start中,我們需要注冊一個緩存項,并讓它在兩分鐘后失效。請注意,你設置回調的失效時間的最小值是兩分鐘。盡管你可以設置一個更小的值,但它似乎不會工作。出現該問題最大的可能是,asp.net工作進程每兩分鐘才查看一次緩存項。





  1. private const string DummyCacheItemKey = "GagaGuguGigi";


  2. protected void Application_Start(Object sender, EventArgs e)

  3. {

  4. RegisterCacheEntry();

  5. }


  6. private bool RegisterCacheEntry()

  7. {

  8. if( null != HttpContext.Current.Cache[ DummyCacheItemKey ] ) return false;


  9. HttpContext.Current.Cache.Add( DummyCacheItemKey, "Test", null,

  10. DateTime.MaxValue, TimeSpan.FromMinutes(1),

  11. CacheItemPriority.Normal,

  12. new CacheItemRemovedCallback( CacheItemRemovedCallback ) );


  13. return true;

  14. }


該緩存實體是一個虛設的實體。我們不需要在這里存儲任何有價值的信息,因為無論我們在這里存儲什么,他們都有可能在應用程序重啟時丟失。另外,我們所需要的只是使該項的頻繁回調。


在回調的內部,我們就可以完成“計劃任務”:





  1. public void CacheItemRemovedCallback( string key,

  2. object value, CacheItemRemovedReason reason)

  3. {

  4. Debug.WriteLine("Cache item callback: " + DateTime.Now.ToString() );


  5. DoWork();

  6. }


在緩存項失效時再次存儲緩存項


無論何時緩存項失效,我們都能夠獲得一個回調同時該項將永久地從緩存中消失。所以,我們將不能再次獲得回調了。為了能提供一個持續的回調,我們需要在下次失效之前重新存儲一個緩存項。這看起來似乎相當容易:我們可以在回調函數中調用我們上面展示的RegisterCacheEntry方法,可以這么做嗎?它不會工作!當回調發生,HttpContext已經無法訪問。HttpContext僅僅在一個請求正在被處理的時候才可以被訪問。因為回調發生在web服務器的幕后,所以這里沒有請求需要被處理,因而HttpContext對象無法獲得。因此,你也無法從回調中訪問Cache對象。


方案是,我們需要一個簡單的請求。我們可以利用.netFramework中的WebClient類來實現一個對虛擬頁面的“虛擬”訪問。當虛擬頁面被執行,我們可以Hold住HttpContext對象,然后再次注冊一個緩存項的回調。


所以,回調方法作一點修改來發出一個虛擬調用。





  1. public void CacheItemRemovedCallback( string key,

  2. object value, CacheItemRemovedReason reason)

  3. {

  4. Debug.WriteLine("Cache item callback: " + DateTime.Now.ToString() );


  5. HitPage();


  6. // Do the service works


  7. DoWork();

  8. }

HitPage方法對一個虛擬頁面發出調用:






  1. private const string DummyPageUrl =

  2. "http://localhost/TestCacheTimeout/WebForm1.aspx";


  3. private void HitPage()

  4. {

  5. WebClient client = new WebClient();

  6. client.DownloadData(DummyPageUrl);

  7. }
無論虛擬頁面在什么時候被調用,Application_BeginRequest方法都將被調用。在那里,我們可以核查是否它是一個“虛擬”頁面。






  1. protected void Application_BeginRequest(Object sender, EventArgs e)

  2. {

  3. // If the dummy page is hit, then it means we want to add another item


  4. // in cache


  5. if( HttpContext.Current.Request.Url.ToString() == DummyPageUrl )

  6. {

  7. // Add the item in cache and when succesful, do the work.


  8. RegisterCacheEntry();

  9. }

  10. }


我們僅僅截獲虛擬頁面的請求,并且讓其他的頁面以他們原來的方式繼續執行。


Web進程重啟時重啟緩存項回調


這里有很多情況,可能導致web服務器重啟。例如,如果系統管理員重啟IIS,或者重啟電腦,或者web進程陷入死循環(在windows 2003下)。在這樣的情況下,服務將停止運行,直到一個頁面被請求和Application_Start被調用。Application_Start僅僅在當一個頁面第一次被訪問時才會被調用。所以,當web進程被重啟時為了讓“服務”運行起來,我們只能手動調用“虛擬”頁面,或者某人需要訪問你站點的主頁。


一個“滑頭”的方案是:可以把搜索引擎加入你的站點中。搜索引擎時常會爬行頁面。因此,它們將訪問你站點的一個網頁,這就可以觸發Application_Start的執行,因此服務將被再次啟動運行。


另一個方案是向某些通信或可用性監控服務注冊你的站點。有許多關注你站點以及可以檢查你的站點是否正常并且性能是否良好的Web 服務。所有這些服務都需要訪問你站點的頁面然后收集統計信息。所以,通過注冊這樣的服務,你可以保證你的站點一直“存活”著。


測試可執行任務的類型


讓我們來測試一下,是否我們能夠做一個windowsservice能夠做的一切任務。首先,第一個問題是,我們不能做一個windows service能夠做的所有事情,因為windowsservice運行在一個本地系統賬戶的權限下。這是一個具有非常高權限的賬戶,使用這個賬戶你可以在你的系統中做任何事情。然而,asp.net web線程運行在ASPNET賬戶下(windows xp)或者NETWORKSERVICE賬戶下(windows 2003)。這是一個低權限的賬戶,并且沒有權限訪問硬盤。為了允許服務向硬盤寫東西,web進程需要被授予對文件夾的寫權限。我們都知道關于此的安全問題,所以我將不再詳述細節。


現在,我們將開始測試我們通常利用windowsservice完成的事情:


(1) 向文件寫東西


(2) 數據庫操作


(3) Web Service調用


(4) MSMQ 操作


(5) Email 發送


讓我們來寫一些測試代碼:





  1. private void DoWork()

  2. {

  3. Debug.WriteLine("Begin DoWork...");

  4. Debug.WriteLine("Running as: " +

  5. WindowsIdentity.GetCurrent().Name );


  6. DoSomeFileWritingStuff();

  7. DoSomeDatabaseOperation();

  8. DoSomeWebserviceCall();

  9. DoSomeMSMQStuff();

  10. DoSomeEmailSendStuff();


  11. Debug.WriteLine("End DoWork...");

  12. }

測試文件“寫”操作

讓我們來測試一下是否我們真的能夠向文件內寫東西。在C盤創建一個文件夾,將其命名為“temp”(如果磁盤的格式是NTFS,允許ASPNET/NETWORKSERVICE賬戶向該文件夾的寫權限)。





  1. private void DoSomeFileWritingStuff()

  2. {

  3. Debug.WriteLine("Writing to file...");


  4. try

  5. {

  6. using( StreamWriter writer =

  7. new StreamWriter(@"c:\temp\Cachecallback.txt", true) )

  8. {

  9. writer.WriteLine("Cache Callback: {0}", DateTime.Now);

  10. writer.Close();

  11. }

  12. }

  13. catch( Exception x )

  14. {

  15. Debug.WriteLine( x );

  16. }


  17. Debug.WriteLine("File write successful");

  18. }


打開該文件,然后你應該看到這樣的信息:





  1. Cache Callback: 10/17/2005 2:50:00 PM

  2. Cache Callback: 10/17/2005 2:52:00 PM

  3. Cache Callback: 10/17/2005 2:54:00 PM

  4. Cache Callback: 10/17/2005 2:56:00 PM

  5. Cache Callback: 10/17/2005 2:58:00 PM

  6. Cache Callback: 10/17/2005 3:00:00 PM

測試數據庫的可連接性


在你的“tempdb”數據庫中運行下面的代碼(也可以自己建數據庫測試)





  1. IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id =

  2. object_id(N'[dbo].[ASPNETServiceLog]') AND

  3. OBJECTPROPERTY(id, N'IsUserTable') = 1)

  4. DROP TABLE [dbo].[ASPNETServiceLog]

  5. GO


  6. CREATE TABLE [dbo].[ASPNETServiceLog] (

  7. [Mesage] [varchar] (1000)

  8. COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,

  9. [DateTime] [datetime] NOT NULL

  10. ) ON [PRIMARY]

  11. GO

上面的代碼將創建一個名為ASPNETServiceLog的表。記住,因為該表創建于tempdb中,所以該表在SQL Server重啟的時候將消失。


接下來,為ASPNET/NETWORKSERVICE賬戶授予tempdb數據庫的db_datawriter權限。另外,你可以定義更多特殊的權限,并且只允許往表中寫權限。


現在,寫下測試方法:






  1. private void DoSomeDatabaseOperation()

  2. {

  3. Debug.WriteLine("Connecting to database...");


  4. using( SqlConnection con = new SqlConnection("Data Source" +

  5. "=(local);Initial Catalog=tempdb;Integrated Security=SSPI;") )

  6. {

  7. con.Open();


  8. using( SqlCommand cmd = new SqlCommand( "INSERT" +

  9. " INTO ASPNETServiceLog VALUES" +

  10. " (@Message, @DateTime)", con ) )

  11. {

  12. cmd.Parameters.Add("@Message", SqlDbType.VarChar, 1024).Value =

  13. "Hi I'm the ASP NET Service";

  14. cmd.Parameters.Add("@DateTime", SqlDbType.DateTime).Value =

  15. DateTime.Now;


  16. cmd.ExecuteNonQuery();

  17. }


  18. con.Close();

  19. }


  20. Debug.WriteLine("Database connection successful");

  21. }


這將在log表中產生一些記錄,你可以測試來確保“服務”的執行是否有延遲。你應該會再每兩分鐘獲得一行數據。



測試郵件的分發


對運行一個windows service最基本的需求是定期發送郵件提醒,狀態報告等等。所以,測試是否可以像windows service一樣發送email很重要:






  1. private void DoSomeEmailSendStuff()

  2. {

  3. try

  4. {

  5. MailMessage msg = new MailMessage();

  6. msg.From = "abc@cde.fgh";

  7. msg.To = "ijk@lmn.opq";

  8. msg.Subject = "Reminder: " + DateTime.Now.ToString();

  9. msg.Body = "This is a server generated message";


  10. SmtpMail.Send( msg );

  11. }

  12. catch( Exception x )

  13. {

  14. Debug.WriteLine( x );

  15. }

  16. }


請將From和To 修改為某些有效的地址,并且你應該每兩分鐘就可以收到一次郵件提醒。


測試MSMQ


讓我們寫一個簡單的方法來測試是否我們可以從asp.net直接訪問MSMQ:





  1. private void DoSomeMSMQStuff()

  2. {

  3. using( MessageQueue queue = new MessageQueue(MSMQ_NAME) )

  4. {

  5. queue.Send(DateTime.Now);

  6. queue.Close();

  7. }

  8. }

另外,你可以調用隊列的Receive方法來解析隊列中需要被處理的消息。


這里,有一個你必須記住的問題是,不要訂閱隊列的Receive事件。因為線程可能隨時會被殺死,并且web服務器可能隨時會被重啟,一個持續阻塞的Receive將不能正常地工作。另外,如果你調用BeginReceive方法同時阻塞代碼的執行直到一個消息到達,服務將被卡住然后其他的代碼將不會再運行。所以,在這種情況下,你將不得不調用Receive方法來解析消息。


擴展系統功能


Asp.net服務可以被用來擴展那些可插拔的任務。你可以從web頁面中引入作業排隊,讓這種服務定期執行。例如,你可以將作業隊列放入一個緩存項,讓“服務”來選擇任務然后執行它。采用這種方式,你可以在你的asp.net項目中實現一個簡單的任務處理系統。


讓我們實現一個簡單的Job類,它包含了一個任務執行的信息。





  1. public class Job

  2. {

  3. public string Title;

  4. public DateTime ExecutionTime;


  5. public Job( string title, DateTime executionTime )

  6. {

  7. this.Title = title;

  8. this.ExecutionTime = executionTime;

  9. }


  10. public void Execute()

  11. {

  12. Debug.WriteLine("Executing job at: " + DateTime.Now );

  13. Debug.WriteLine(this.Title);

  14. Debug.WriteLine(this.ExecutionTime);

  15. }

  16. }
在一個簡單的aspx頁面上,我們將一個任務排入一個定義在Global.Asax中的名為_JobQueue的ArrayList中。





  1. Job newJob = new Job( "A job queued at: " + DateTime.Now,

  2. DateTime.Now.AddMinutes(4) );

  3. lock( Global._JobQueue )

  4. {

  5. Global._JobQueue.Add( newJob );

  6. }
所以,被排入隊列中的任務將在4分鐘之后被執行。該服務的代碼每兩分鐘執行一次,它會檢查任務隊列,是否有任何逾期且需要被執行的任務。如果有任何的任務在等待,它將被從隊列中移除并執行。服務代碼有一個額外的方法,叫做ExecuteQueuedJobs。該方法做定期任務的執行:




  1. private void ExecuteQueuedJobs()

  2. {

  3. ArrayList jobs = new ArrayList();


  4. // Collect which jobs are overdue


  5. foreach( Job job in _JobQueue )

  6. {

  7. if( job.ExecutionTime <= DateTime.Now )

  8. jobs.Add( job );

  9. }


  10. // Execute the jobs that are overdue


  11. foreach( Job job in jobs )

  12. {

  13. lock( _JobQueue )

  14. {

  15. _JobQueue.Remove( job );

  16. }


  17. job.Execute();

  18. }

  19. }


不要忘記鎖住靜態的“任務集合”,因為asp.net是多線程的。并且頁面會在不同的線程上執行,所以同時往任務隊列中寫是很有可能的。


該文章在 2012/1/23 0:57:52 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
午夜男女爽爽刺激视频在线观看 | 五月婷婷88亚洲 | 一本久久a久久精品综合夜夜 | 中文字幕乱码亚州无线码二区 | 在线观看国产日韩亚洲中文字幕 | 中文字幕三级专区 |