asp.net Handler中的IsReusable屬性及在Handler中使用Session

你們在用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中的難點和困惑點,你們能夠參考領會一下。

相關文章
相關標籤/搜索