你們在用HttpHandler的時候,通常都會有兩個大的疑問(固然,前提是你有鑽研精神的話,呵呵)web
1. IsReusable到底什麼意思?編程
老實說,這個屬性不少人都感興趣,但搞懂的人確實很少。MSDN中的介紹也是不知因此然。瀏覽器
獲取一個值,該值指示其餘請求是否可使用 IHttpHandler 實例。該屬性默認爲false安全
我來這麼說吧,首先咱們爲何使用自定義的Handler呢?簡單的說,咱們是但願能接管掉某些請求, 對吧?最多見的應用以下服務器
對圖片進行處理。例如全部圖片都輸出一個水印。或者防止盜鏈到設計併發
添加一些特殊的擴展名。例如,個人網站能不能有一個後綴名爲chenxizhang的網頁呢?(這固然是一 個比喻,事實上通常沒有必要這麼作)異步
知道上述的需求以後,咱們再來看一下後臺的設計。HttpHandler其實就是實現了IHttpHandler接口的 一個類型,它要工做,就必須經過 ASP.NET所提供的一些所謂的Factory去建立實例,而後調用它的 ProcessRequest方法。其實就這麼簡單性能
由於建立對象實例在服務器確定是須要佔用資源的,那麼咱們就勢必要考慮這些請求能不能在必定程 序上去複用。這就是IsReusable的初衷測試
事實上,咱們對這種複用並不會陌生。日常咱們就知道對象池和鏈接池的技術。Handler的Reuse也是 一個池的概念。網站
好了,說了這麼一堆的概念,咱們來說一講該屬性設置爲true和設置爲false的區別。
設置爲true,則一般狀況下,就建立一次實例
設置爲false,則每次請求都須要建立實例
咱們來看一個例子。爲了作測試,我寫了以下代碼
using System; using System.Collections.Generic; using System.Web; using System.IO; using System.Threading; namespace WebApplication1 { public class TestHandler:IHttpHandler { #region IHttpHandler 成員 public bool IsReusable { get { return fasle; } } public void ProcessRequest(HttpContext context) { WriteLogMessage("當前被調用的請求是:" + context.Request.Path); context.Response.Write("你請求的地址是:" + context.Request.Url.ToString() + ""); WriteLogMessage("結束調用"); } #endregion public TestHandler() { WriteLogMessage("對象實例被建立"); } void WriteLogMessage(string msg) { string logFile = HttpContext.Current.Server.MapPath("Log.txt"); File.AppendAllText(logFile, DateTime.Now.ToString()+ msg + Environment.NewLine); } } }
代碼很簡單,我經過一個文本文件的方式記錄對象什麼建立的,何時發起請求和結束請求的。現 在我先設置IsReusable爲false
在調試以前,咱們還須要設置一下web.config,添加一個HttpHandler的註冊
這裏做爲演示目的,我就犧牲一下本身,用這個Handler來監聽全部後綴名爲chenxizhang的請求
打開瀏覽器,輸入相似下面這樣的網址http://localhost:6994/test.chenxizhang
快速地刷新幾回,而後關閉瀏覽器。找到網站根目錄下面的一個Log.txt文件,會看到下面的文本
2009/6/15 11:28:30對象實例被建立 2009/6/15 11:28:30當前被調用的請求是:/test.chenxizhang 2009/6/15 11:28:30結束調用 2009/6/15 11:28:31對象實例被建立 2009/6/15 11:28:31當前被調用的請求是:/test.chenxizhang 2009/6/15 11:28:31結束調用 2009/6/15 11:28:31對象實例被建立 2009/6/15 11:28:31當前被調用的請求是:/test.chenxizhang 2009/6/15 11:28:31結束調用 2009/6/15 11:28:32對象實例被建立 2009/6/15 11:28:32當前被調用的請求是:/test.chenxizhang 2009/6/15 11:28:32結束調用 2009/6/15 11:28:32對象實例被建立 2009/6/15 11:28:32當前被調用的請求是:/test.chenxizhang 2009/6/15 11:28:32結束調用 2009/6/15 11:28:32對象實例被建立 2009/6/15 11:28:32當前被調用的請求是:/test.chenxizhang 2009/6/15 11:28:32結束調用 2009/6/15 11:28:32對象實例被建立 2009/6/15 11:28:32當前被調用的請求是:/test.chenxizhang
咱們能夠看到,每次都請求都須要建立實例
而後,咱們去修改一下IsReusable屬性爲true,再運行,就能夠看到下面這樣的輸出結果了
2009/6/15 11:23:34對象實例被建立 2009/6/15 11:23:34當前被調用的請求是:/test.chenxizhang 2009/6/15 11:23:34結束調用 2009/6/15 11:24:40當前被調用的請求是:/test.chenxizhang 2009/6/15 11:24:40結束調用 2009/6/15 11:24:40當前被調用的請求是:/test.chenxizhang 2009/6/15 11:24:40結束調用 2009/6/15 11:24:40當前被調用的請求是:/test.chenxizhang 2009/6/15 11:24:40結束調用 2009/6/15 11:24:40當前被調用的請求是:/test.chenxizhang 2009/6/15 11:24:40結束調用 2009/6/15 11:24:40當前被調用的請求是:/test.chenxizhang 2009/6/15 11:24:40結束調用 2009/6/15 11:24:41當前被調用的請求是:/test.chenxizhang 2009/6/15 11:24:41結束調用 2009/6/15 11:24:41當前被調用的請求是:/test.chenxizhang 2009/6/15 11:24:41結束調用
也就是說,對象只被建立了一次。後面調用就重複利用了。這樣看起來是否是還不錯呢?
一般意義上說,IsReusable設置爲true能夠提升性能,尤爲是說這種Handler初始化的時候須要作不少 事情的狀況下。但爲何默認又設置爲false呢?
由於若是設置爲true,也就是全部有用戶,全部請求都共享一個對象實例,那麼可能形成什麼問題呢 ?
最典型的問題就是你要注意這個類型中成員變量的線程安全性問題,例如某個請求上來以後,修改了 某個變量;而後作其餘的事情,緊接着其餘請求也上來了,它又修改了變量,這可能就有問題。
這一點我倒認爲沒有什麼大不了的,我也算是寫過一些Handler的,但其實比較少真的複雜到要搞一堆 變量。若是是那樣,是須要反省的
我同時還想到另一個問題,假設IsReusable是設置爲true的,同時又假設它的ProcessRequest卻又 須要比較長時間才能完成。那麼會怎麼樣呢?
就是說前一個請求尚未完成,這時候又有第二個請求過來。該如何處理?
using System; using System.Collections.Generic; using System.Web; using System.IO; using System.Threading; using System.Web.SessionState; namespace WebApplication1 { public class TestHandler:IHttpHandler { #region IHttpHandler 成員 public bool IsReusable { get { return true; } } public void ProcessRequest(HttpContext context) { WriteLogMessage("當前被調用的請求是:" + context.Request.Path); context.Response.Write("你請求的地址是:" + context.Request.Url.ToString() + ""); Thread.Sleep(10000); WriteLogMessage("結束調用"); } #endregion public TestHandler() { WriteLogMessage("對象實例被建立"); } void WriteLogMessage(string msg) { string logFile = HttpContext.Current.Server.MapPath("Log.txt"); File.AppendAllText(logFile, DateTime.Now.ToString()+ msg + Environment.NewLine); } } }
上面的代碼中,我加入一個讓當前線程休眠的代碼:讓它休眠10秒鐘。此時,咱們來刷新瀏覽器看看 會怎麼樣
2009/6/15 12:05:59對象實例被建立 2009/6/15 12:05:59當前被調用的請求是:/ie.chenxizhang 2009/6/15 12:06:02對象實例被建立 2009/6/15 12:06:02當前被調用的請求是:/google.chenxizhang 2009/6/15 12:06:09結束調用 //這一句是ie.chenxizhang結束了 2009/6/15 12:06:12結束調用 //這一句是google.chenxizhang結束了
我用了兩個瀏覽器來模擬兩個用戶的操做。你會驚奇地發現,雖然咱們設置爲true,但仍然建立了多 個實例。這又是咋回事呢
這個問題要這麼理解了:由於如今是線程阻塞了,因此ASP.NET引擎會檢測到這一點,他必須建立另外 實例出來,不然很顯然,第二個請求就必須等第一個請求結束以後才能開始工做,這顯然是不能夠接受的 ,這個現象,在大容量用戶併發的時候多是一個災難
好吧,再繞回來講,假如咱們的操做時間比較長,但何時又不須要建立多個實例呢?答案就是說 ,若是異步的話。
也正由於可能會有異步的狀況,因此就出現了咱們剛纔所說的線程安全性問題。能夠配合鎖定機制避 免一些問題。
因此,針對IsReusable屬性,我總結以下:
這個屬性默認爲false(Visual studio提供的模板默認將其設置爲false)
若是設置爲true,能提升性能,但要注意線程之間安全性問題
若是設置爲false,則線程是安全的
2. 如何在Handler中使用會話狀態(Session)?
有些朋友 在使用Handler的時候,念念不忘原先在頁面編程中的會話狀態(Session),一個典型的問題就是:在 Handler裏面能不能使用個人用戶狀態信息呀?
例如,能不能添加下面這樣的代碼呢
context.Response.Write("當前用戶的狀態值爲:" + context.Session ["Id"].ToString());
答案是:固然能夠。
先別忙着樂,雖然能夠添加這樣的代碼,但它默認卻沒法工做。你會收到下面這樣的錯誤消息:未將 對象設置到引用的實例
其實,一個比較好的建議是:不要在Handler裏面使用會話狀態。甚至在整個網站都不要。
可是,假設你就是認準了這個非用不可,那麼你也能夠經過下面的方式來實現
爲該類型實現一個接口:IRequiresSessionState
這個接口不須要有任何方法實現,只須要定義一下就能夠了。因此整個案例的代碼,以下
using System; using System.Collections.Generic; using System.Web; using System.IO; using System.Threading; using System.Web.SessionState; namespace WebApplication1 { public class TestHandler:IHttpHandler,IRequiresSessionState { #region IHttpHandler 成員 public bool IsReusable { get { return true; } } public void ProcessRequest(HttpContext context) { WriteLogMessage("當前被調用的請求是:" + context.Request.Path); context.Response.Write("你請求的地址是:" + context.Request.Url.ToString() + ""); context.Response.Write("當前用戶的狀態值爲:" + context.Session ["Id"].ToString()); WriteLogMessage("結束調用"); } #endregion public TestHandler() { WriteLogMessage("對象實例被建立"); } void WriteLogMessage(string msg) { string logFile = HttpContext.Current.Server.MapPath("Log.txt"); File.AppendAllText(logFile, DateTime.Now.ToString()+ msg + Environment.NewLine); } } }
以上兩點是編寫HttpHandler中的難點和困惑點,你們能夠參考領會一下。