使用ASP.NET實(shí)現(xiàn)Windows Service定時(shí)執(zhí)行任務(wù)
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
我們?cè)鯓硬拍茉诜?wù)器上使用asp.net定時(shí)執(zhí)行任務(wù)而不需要安裝windows service?我們經(jīng)常需要運(yùn)行一些維護(hù)性的任務(wù)或者像發(fā)送提醒郵件給用戶這樣的定時(shí)任務(wù)。這些僅僅通過使用Windows Service就可以完成。Asp.net通常是一個(gè)無狀態(tài)的提供程序,不支持持續(xù)運(yùn)行代碼或者定時(shí)執(zhí)行某段代碼。所以,我們不得不構(gòu)建自己的windows service來運(yùn)行那些定時(shí)任務(wù)。但是在一個(gè)共享的托管環(huán)境下,我們并不總是有機(jī)會(huì)部署我們自己的windwos service到我們托管服務(wù)提供商的web服務(wù)器上。我們要么買一個(gè)專用的服務(wù)器,當(dāng)然這是非常昂貴的,要么就犧牲我們網(wǎng)站的一些功能。然而,運(yùn)行一個(gè)定期執(zhí)行的任務(wù)是一個(gè)非常有用的功能,特別是對(duì)那些需要發(fā)送提醒郵件的用戶、需要維護(hù)報(bào)表以及運(yùn)行清理操作的的管理員而言。我將給你展示一種無須使用任何windows service,僅僅采用asp.net來運(yùn)行定期任務(wù)的方式。
它怎樣工作 首先,我們需要asp.net中的某些“場(chǎng)景”,能夠持續(xù)不斷地運(yùn)行并且給我們一個(gè)回調(diào)。而IIS上的web服務(wù)器就是一個(gè)很不錯(cuò)的選擇。所以,我們需要從它那里很“頻繁”地獲得回調(diào),這樣我們可以查看一個(gè)任務(wù)隊(duì)列,并且能夠看到是否有任務(wù)需要執(zhí)行。現(xiàn)在,這里有一些方式可以為我們獲得對(duì)web服務(wù)器的“操作權(quán)”: (1) 當(dāng)一個(gè)頁面被請(qǐng)求 (2) 當(dāng)一個(gè)應(yīng)用程序被啟動(dòng) (3) 當(dāng)一個(gè)應(yīng)用程序被停止 (4) 當(dāng)一個(gè)會(huì)話開啟、結(jié)束或者超時(shí) (5) 當(dāng)一個(gè)緩存項(xiàng)失效 一個(gè)頁面被請(qǐng)求是隨機(jī)的。如果幾個(gè)小時(shí)內(nèi)沒有人訪問你的站點(diǎn),那么幾個(gè)小時(shí)內(nèi)你都無法完成任何“任務(wù)”。另外,一個(gè)請(qǐng)求的執(zhí)行時(shí)間是非常短的,并且它本身也需要越快越好。如果你計(jì)劃在頁面請(qǐng)求的時(shí)候執(zhí)行“計(jì)劃任務(wù)”,這樣頁面將會(huì)被迫執(zhí)行很長時(shí)間,這將導(dǎo)致一個(gè)很糟糕的用戶體驗(yàn)。所以,選擇在頁面請(qǐng)求的時(shí)機(jī)做這樣的操作不是一個(gè)好的選擇。 一個(gè)頁面被請(qǐng)求是隨機(jī)的。如果幾個(gè)小時(shí)內(nèi)沒有人訪問你的站點(diǎn),那么幾個(gè)小時(shí)內(nèi)你都無法完成任何“任務(wù)”。另外,一個(gè)請(qǐng)求的執(zhí)行時(shí)間是非常短的,并且它本身也需要越快越好。如果你計(jì)劃在頁面請(qǐng)求的時(shí)候執(zhí)行“計(jì)劃任務(wù)”,這樣頁面將會(huì)被迫執(zhí)行很長時(shí)間,這將導(dǎo)致一個(gè)很糟糕的用戶體驗(yàn)。所以,選擇在頁面請(qǐng)求的時(shí)機(jī)做這樣的操作不是一個(gè)好的選擇。 當(dāng)一個(gè)應(yīng)該程序啟動(dòng)時(shí),Global.asax內(nèi)的 當(dāng)一個(gè)應(yīng)用程序停止的時(shí)候,我們同樣可以從Application_End方法獲得一個(gè)回調(diào)。但是我們?cè)谶@里卻不能做任何事情,因?yàn)檎麄€(gè)應(yīng)該程序都已經(jīng)快要結(jié)束運(yùn)行了。Global.asax里的Session_Start會(huì)在當(dāng)一個(gè)用戶訪問一個(gè)需要被實(shí)例化為新會(huì)話的頁面時(shí)被觸發(fā)。所以這也是一個(gè)隨機(jī)事件。而我們需要一個(gè)能持久且定期運(yùn)行的“場(chǎng)景”。 一個(gè)緩存項(xiàng)的失效可以提供一個(gè)時(shí)間點(diǎn)或持續(xù)時(shí)間。在 asp.net 中你可以在 Cache 對(duì)象中增加一個(gè)實(shí)體,并且可以設(shè)置一個(gè)絕對(duì)失效時(shí)間,或者設(shè)置當(dāng)其被從緩存中移除后失效。你可以利用下面的 Cache 類中的方法來做這些:
這意味著,我們可以在一個(gè)緩存項(xiàng)失效時(shí)模擬一個(gè)簡單的windows service。 創(chuàng)建緩存項(xiàng)的回調(diào) 首先,在Application_Start中,我們需要注冊(cè)一個(gè)緩存項(xiàng),并讓它在兩分鐘后失效。請(qǐng)注意,你設(shè)置回調(diào)的失效時(shí)間的最小值是兩分鐘。盡管你可以設(shè)置一個(gè)更小的值,但它似乎不會(huì)工作。出現(xiàn)該問題最大的可能是,asp.net工作進(jìn)程每兩分鐘才查看一次緩存項(xiàng)。
該緩存實(shí)體是一個(gè)虛設(shè)的實(shí)體。我們不需要在這里存儲(chǔ)任何有價(jià)值的信息,因?yàn)闊o論我們?cè)谶@里存儲(chǔ)什么,他們都有可能在應(yīng)用程序重啟時(shí)丟失。另外,我們所需要的只是使該項(xiàng)的頻繁回調(diào)。 在回調(diào)的內(nèi)部,我們就可以完成“計(jì)劃任務(wù)”:
在緩存項(xiàng)失效時(shí)再次存儲(chǔ)緩存項(xiàng) 無論何時(shí)緩存項(xiàng)失效,我們都能夠獲得一個(gè)回調(diào)同時(shí)該項(xiàng)將永久地從緩存中消失。所以,我們將不能再次獲得回調(diào)了。為了能提供一個(gè)持續(xù)的回調(diào),我們需要在下次失效之前重新存儲(chǔ)一個(gè)緩存項(xiàng)。這看起來似乎相當(dāng)容易:我們可以在回調(diào)函數(shù)中調(diào)用我們上面展示的RegisterCacheEntry方法,可以這么做嗎?它不會(huì)工作!當(dāng)回調(diào)發(fā)生,HttpContext已經(jīng)無法訪問。HttpContext僅僅在一個(gè)請(qǐng)求正在被處理的時(shí)候才可以被訪問。因?yàn)榛卣{(diào)發(fā)生在web服務(wù)器的幕后,所以這里沒有請(qǐng)求需要被處理,因而HttpContext對(duì)象無法獲得。因此,你也無法從回調(diào)中訪問Cache對(duì)象。 方案是,我們需要一個(gè)簡單的請(qǐng)求。我們可以利用.netFramework中的WebClient類來實(shí)現(xiàn)一個(gè)對(duì)虛擬頁面的“虛擬”訪問。當(dāng)虛擬頁面被執(zhí)行,我們可以Hold住HttpContext對(duì)象,然后再次注冊(cè)一個(gè)緩存項(xiàng)的回調(diào)。 所以,回調(diào)方法作一點(diǎn)修改來發(fā)出一個(gè)虛擬調(diào)用。
HitPage方法對(duì)一個(gè)虛擬頁面發(fā)出調(diào)用:
我們僅僅截獲虛擬頁面的請(qǐng)求,并且讓其他的頁面以他們?cè)瓉淼姆绞嚼^續(xù)執(zhí)行。 Web進(jìn)程重啟時(shí)重啟緩存項(xiàng)回調(diào) 這里有很多情況,可能導(dǎo)致web服務(wù)器重啟。例如,如果系統(tǒng)管理員重啟IIS,或者重啟電腦,或者web進(jìn)程陷入死循環(huán)(在windows 2003下)。在這樣的情況下,服務(wù)將停止運(yùn)行,直到一個(gè)頁面被請(qǐng)求和Application_Start被調(diào)用。Application_Start僅僅在當(dāng)一個(gè)頁面第一次被訪問時(shí)才會(huì)被調(diào)用。所以,當(dāng)web進(jìn)程被重啟時(shí)為了讓“服務(wù)”運(yùn)行起來,我們只能手動(dòng)調(diào)用“虛擬”頁面,或者某人需要訪問你站點(diǎn)的主頁。 一個(gè)“滑頭”的方案是:可以把搜索引擎加入你的站點(diǎn)中。搜索引擎時(shí)常會(huì)爬行頁面。因此,它們將訪問你站點(diǎn)的一個(gè)網(wǎng)頁,這就可以觸發(fā)Application_Start的執(zhí)行,因此服務(wù)將被再次啟動(dòng)運(yùn)行。 另一個(gè)方案是向某些通信或可用性監(jiān)控服務(wù)注冊(cè)你的站點(diǎn)。有許多關(guān)注你站點(diǎn)以及可以檢查你的站點(diǎn)是否正常并且性能是否良好的Web 服務(wù)。所有這些服務(wù)都需要訪問你站點(diǎn)的頁面然后收集統(tǒng)計(jì)信息。所以,通過注冊(cè)這樣的服務(wù),你可以保證你的站點(diǎn)一直“存活”著。 測(cè)試可執(zhí)行任務(wù)的類型 讓我們來測(cè)試一下,是否我們能夠做一個(gè)windowsservice能夠做的一切任務(wù)。首先,第一個(gè)問題是,我們不能做一個(gè)windows service能夠做的所有事情,因?yàn)閣indowsservice運(yùn)行在一個(gè)本地系統(tǒng)賬戶的權(quán)限下。這是一個(gè)具有非常高權(quán)限的賬戶,使用這個(gè)賬戶你可以在你的系統(tǒng)中做任何事情。然而,asp.net web線程運(yùn)行在ASPNET賬戶下(windows xp)或者NETWORKSERVICE賬戶下(windows 2003)。這是一個(gè)低權(quán)限的賬戶,并且沒有權(quán)限訪問硬盤。為了允許服務(wù)向硬盤寫東西,web進(jìn)程需要被授予對(duì)文件夾的寫權(quán)限。我們都知道關(guān)于此的安全問題,所以我將不再詳述細(xì)節(jié)。 現(xiàn)在,我們將開始測(cè)試我們通常利用windowsservice完成的事情: (1) 向文件寫東西 (2) 數(shù)據(jù)庫操作 (3) Web Service調(diào)用 (4) MSMQ 操作 (5) Email 發(fā)送 讓我們來寫一些測(cè)試代碼:
測(cè)試文件“寫”操作 讓我們來測(cè)試一下是否我們真的能夠向文件內(nèi)寫東西。在C盤創(chuàng)建一個(gè)文件夾,將其命名為“temp”(如果磁盤的格式是NTFS,允許ASPNET/NETWORKSERVICE賬戶向該文件夾的寫權(quán)限)。
打開該文件,然后你應(yīng)該看到這樣的信息:
測(cè)試數(shù)據(jù)庫的可連接性 在你的“tempdb”數(shù)據(jù)庫中運(yùn)行下面的代碼(也可以自己建數(shù)據(jù)庫測(cè)試)
上面的代碼將創(chuàng)建一個(gè)名為ASPNETServiceLog的表。記住,因?yàn)樵摫韯?chuàng)建于tempdb中,所以該表在SQL Server重啟的時(shí)候?qū)⑾А?/P> 接下來,為ASPNET/NETWORKSERVICE賬戶授予tempdb數(shù)據(jù)庫的db_datawriter權(quán)限。另外,你可以定義更多特殊的權(quán)限,并且只允許往表中寫權(quán)限。 現(xiàn)在,寫下測(cè)試方法:
這將在log表中產(chǎn)生一些記錄,你可以測(cè)試來確保“服務(wù)”的執(zhí)行是否有延遲。你應(yīng)該會(huì)再每兩分鐘獲得一行數(shù)據(jù)。 測(cè)試郵件的分發(fā) 對(duì)運(yùn)行一個(gè)windows service最基本的需求是定期發(fā)送郵件提醒,狀態(tài)報(bào)告等等。所以,測(cè)試是否可以像windows service一樣發(fā)送email很重要:
請(qǐng)將From和To 修改為某些有效的地址,并且你應(yīng)該每兩分鐘就可以收到一次郵件提醒。 測(cè)試MSMQ 讓我們寫一個(gè)簡單的方法來測(cè)試是否我們可以從asp.net直接訪問MSMQ:
另外,你可以調(diào)用隊(duì)列的Receive方法來解析隊(duì)列中需要被處理的消息。 這里,有一個(gè)你必須記住的問題是,不要訂閱隊(duì)列的Receive事件。因?yàn)榫€程可能隨時(shí)會(huì)被殺死,并且web服務(wù)器可能隨時(shí)會(huì)被重啟,一個(gè)持續(xù)阻塞的Receive將不能正常地工作。另外,如果你調(diào)用BeginReceive方法同時(shí)阻塞代碼的執(zhí)行直到一個(gè)消息到達(dá),服務(wù)將被卡住然后其他的代碼將不會(huì)再運(yùn)行。所以,在這種情況下,你將不得不調(diào)用Receive方法來解析消息。 擴(kuò)展系統(tǒng)功能 Asp.net服務(wù)可以被用來擴(kuò)展那些可插拔的任務(wù)。你可以從web頁面中引入作業(yè)排隊(duì),讓這種服務(wù)定期執(zhí)行。例如,你可以將作業(yè)隊(duì)列放入一個(gè)緩存項(xiàng),讓“服務(wù)”來選擇任務(wù)然后執(zhí)行它。采用這種方式,你可以在你的asp.net項(xiàng)目中實(shí)現(xiàn)一個(gè)簡單的任務(wù)處理系統(tǒng)。 讓我們實(shí)現(xiàn)一個(gè)簡單的Job類,它包含了一個(gè)任務(wù)執(zhí)行的信息。
不要忘記鎖住靜態(tài)的“任務(wù)集合”,因?yàn)閍sp.net是多線程的。并且頁面會(huì)在不同的線程上執(zhí)行,所以同時(shí)往任務(wù)隊(duì)列中寫是很有可能的。 該文章在 2012/1/23 0:57:52 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |