The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

在寫 ASP.NET 的時候要謹慎使用靜態(static)欄位

最近發現了一個之前開發過的網站,大約兩、三天就會發生「已經開啟一個與這個 Command 相關的 DataReader,必須先將它關閉。」的錯誤訊息,整個網站可以正常編譯,代表語法沒問題,測試的時候可以正常執行也不會發生 Exception,但只要一到了客戶那邊的主機執行個幾天就會開始出現問題,但通常多 reload 幾次網頁就會正常執行就好了,完全是一個很詭異的狀況。

程式碼很簡單,如下:

public class Counter
{
    protected static ExtensionDataSetTableAdapters.ClickBlogCounterTableAdapter 
        ClickBlogCounterTA = new ExtensionDataSetTableAdapters.ClickBlogCounterTableAdapter();

    public static int getClickBlogCount()
    {
        return (int)ClickBlogCounterTA.getCount();
    }
}

錯誤出現在 "return (int)ClickBlogCounterTA.getCount();" 這一行,其中 ClickBlogCounterTA 是在 Typed DataSet 中的一個 TableAdapter 物件,而這個靜態方法是在 App_Code 底下的一個類別中。

完整的錯誤訊息如下:

System.Web.HttpUnhandledException: 已發生類型 'System.Web.HttpUnhandledException' 的例外狀況。 ---> System.InvalidOperationException: 已經開啟一個與這個 Command 相關的 DataReader,必須先將它關閉。
   於 System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)
   於 System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String method, SqlCommand command)
   於 System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async)
   於 System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
   於 System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   於 ExtensionDataSetTableAdapters.ClickBlogCounterTableAdapter.getCount() 於 c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\root\bcc246a4\3746b4ea\App_Code.eopvwaqp.10.cs: 行 8729
   於 Counter.getClickBlogCount() 於 c:\AAA\BBB\App_Code\Counter.cs: 行 62
   ......

這段 Code 不是我寫的,加上最近有點忙,所以問題一直擱著,但我找了個幾個朋友讓他們看 Code 請他們找出問題所在,但他們也都覺得這段 Code 沒問題,大家也提出一些「想法」與「解決方案」,大多人提供兩點建議:

  1. 加上 try / catch 就好啦:基本上,這是最爛的答案了,這等於是逃避問題嘛!不過這也是最懶、最快、最省事、也最輕鬆的解決方案了,但這不是我要的選項。
  2. 這一看就知道是微軟的問題嘛,錯誤是在 Typed DataSet 自動產生的程式碼中出錯的啊:ㄟ...,我也曾經一度懷疑啦,不過就以往的經驗來說,通常都是自己寫的程式錯誤的機率比較高,這應該是排在最後的一個選項了,若真的是這樣,就只好用第一點的建議了。^_^

就因為錯誤不容易重現,程式碼又可以正常執行,我昨天花了些時間仔細推敲程式碼的結構,最後終於看出問題所在,並將這個問題徹底解決了。

首先有個很重要的觀念需要說明,若你在 ASP.NET 中使用靜態欄位(static field)的話,這個靜態欄位物件會一直留存在 ASP.NET 的應用程式中 (HttpApplication),也就是說當 ASP.NET 頁面處理完成之後,該物件還是一樣存在 HttpApplication 中,並且所有 HTTP Request 都會共用這個靜態欄位變數,所以只要網站流量變多的時候就很有可能會發生資源衝突的情況,導致 TableAdapter 在進行資料處理的時候因為只共用一條連線,而造成連線狀態彼此衝突而混亂的情況。

我個人在寫 ASP.NET 就是因為知道會有這種狀況,所以本身就很少用 static 宣告變數,除非你真的很清楚使用 static 變數的時機與潛在會發生的問題,就可以大膽去用啦。

講到這裡,讓我想到一個好玩的排行榜,就是當程式不會動或有問題的時候,通常程式設計師的回答如下:

  • 第 20 名:這很奇怪喔。
  • 第 19 名:以前從來不會這樣啊!
  • 第 18 名:昨天明明會動的啊!
  • 第 17 名:怎麼可能~
  • 第 16 名:這一定是機器的問題。
  • 第 15 名:你到底是打了什麼才讓程式當掉的?
  • 第 14 名:一定是你的資料有問題。
  • 第 13 名:我已經好幾個禮拜沒碰那一段程式了。
  • 第 12 名:你一定是用到舊版了。
  • 第 11 名:一定是巧合!為什麼這種壞運氣只讓你碰上。
  • 第 10 名:我不可能什麼功能都測試到吧,有 bug 是正常的!
  • 第 9 名:這個不可能是那個的原始碼!
  • 第 8 名:這程式應該是會動的,只是我寫好後還沒做測試。
  • 第 7 名:可惡!一定有人改了我的程式。
  • 第 6 名:你有檢查過你的電腦有沒有病毒嗎?
  • 第 5 名:儘管這功能還不能動啦,你覺得他如何?
  • 第 4 名:在你的系統不能用那一個版本的程式啦!
  • 第 3 名:你幹嘛要那樣操作,都是你的問題。
  • 第 2 名:程式發生問題時你在哪裡?
  • 第 1 名:在我的機器明明就可以動啊!
  • 萬用答案:電腦請重開,應該就會好了!

另外,工程師常說的話還有以下幾項,你也可以看看你常說哪幾項:

  • 25.都這樣了,還不work,搞什麼?
  • 24.你可能中毒了喔。
  • 23.一定是有人改了我的程式。
  • 22.已經可以了,不過還沒測試過喔。
  • 21.都好了啊,還沒測試過就是了。
  • 20.我不是已經修好了嗎?
  • 19.這個不能那個。(THIS can't do THAT.)
  • 18.我一個人又測不完!
  • 17.怎麼這麼衰!
  • 16.沒問題,馬上好!
  • 15.當然,當然,我再修一修就可以了。
  • 14.快好了,快好了。
  • 13.好啦,只不過一個小功能嘛!
  • 12.你拿錯執行檔了。
  • 11.可以,可以,來得及。
  • 10.我可沒動過這個模組喔!
  • 9.你的測試資料一定有錯!(我那邊不會啊!)
  • 8.不可以這樣操作的啦!
  • 7.你的作業系統(驅動程式)升級了沒有啊?
  • 6.機器好像壞了。
  • 5.怎麼可能?!
  • 4.哦,這程式還要改一下。
  • 3.昨天還好好的呀!
  • 2.我從來不知道有這種事。
  • 1.奇怪...
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • 最後,也是最常用的
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • 0.更