在 .NET 的世界裡面,無論是Web Apps or Windows Apps,在 runtime 時期都是在 CLR 上運作,而 CLR 所看到的都是在 AppDomain 中執行的 thread,
Web/Windows Apps 的分別只是在於 CLR 載入的 aassembly 不同罷了。
從上上週末開始,我們 Team 就開始設法利用 TLS 來解決舊元件造成的 MSDTC 問題,
在想像中這個 solution 可以同時滿足 Web Apps & Windows Apps ,
沒想到就此一頭栽進了深不見底的 ASP.NET multi-thread 地獄。
(到現在還是處於一頭霧水的狀態 Orz)
經過5個系統的實測(4個是各部門的系統,另一個是自己寫的超小型 demo 網站),
我們發現「ASP.NET 在某些不明條件下,會以 multi-thread 的方式來處理 Request」,
也就是「custom httpModule 與 page 的 code-behind 是以不同的 Thread 在運作」,
這與我們想像中的「ASP.NET 對一個 Request 從頭到尾都是以單一 Thread 來處理」的假設完全不同,而這種現象使得 TLS Solution 呈現完全報廢的狀態,為甚麼呢?
在 What’s ThreadLocalStorage 這篇文章中所附的範例程式中,我是在一個自己撰寫的 custom httpModule 中去處理 TLS 中的資料,若 ASP.NET 的行為一如預期的是以單一 Thread 來處理 Request,那麼在page 的 code-behind 程式 中便可順利取得在 custom httpModule 中存入 TLS 的資料(connection & transaction 物件),也就可以執行 local transactions 而不需勞師動眾的啟動 distributed transaction (MSDTC) 了。
但是當 ASP.NET 以新的 Thread (#2) 來處理 page 的 code-behind 程式時,Thread #2 是無法取得 custom httpModule (Thread #1) 的 TLS 中的 connection & trnasaction 物件的(不然怎麼叫 ThreadLocalStorage 呢?),因此在 code-behind 的程式中要 access DB 時便會出現 NullReferenceException!
奇怪的是,在拿來實測的系統中完全沒有撰寫 multi-thread 的程式(e.g., Thread.Start()),因此目前推測 multi-thread 現象是 ASP.NET 內部所做的 (Performance) Optimization 所產生的結果,是我們的程式無法(起碼很難)控制的。
在5個實測過的系統中,出現以下的 multi-thread 現象:
(System E 是自己寫的超小型 demo 網站)
- 有插入 custom httpModule:
- System A: httpModule 屬於 Thread #1,page 屬於 Thread #2
- System B: httpModule 屬於 Thread #1 & #2,page 屬於 Thread #3 ~ #n
- System C: httpModule 與 page 屬於 Thread #1 (使用單一 Thread!)
- System D: httpModule 屬於 Thread #1,page 屬於 Thread #2
- System E: httpModule 與 page 屬於 Thread #1 (使用單一 Thread!)
- 不插入 custom httpModule (也就是一般寫網站的狀況):
- System A: page 屬於 Thread #1 ~ #n
- System B: page 屬於 Thread #1 (使用單一 Thread!)
- System C: 沒有測到
- System D: page 屬於 Thread #1 (使用單一 Thread!)
- System E: httpModule 與 page 屬於 Thread #1 (使用單一 Thread!)
- .NET Framework 版本 (1.1/2.0/3.5)
- IIS 版本 (6.0/7.0)
- 是否(混合)使用 MasterPage、UserControl、CustomControl
- Page 是否繼承自己包的 BasePage
- 頁面是否有用 frame 切割
- 某種程式寫法會造成 ASP.NET 內部自動改以 multi-thread 來執行
- 在 page 中以 js 另開視窗(做一些事情)之後再關閉的程式寫法
- 在自己寫的超小型 demo 網站中,MasterPage、BasePage、UserControl 中的 Init / OnLoad 事件中的程式碼都是空的,不會啟動 multi-thread
- 註冊 custom httpModule (不註冊的話,使用單一 Thread 的機率高很多)
- 第一次 Request 與 (Ctrl+) F5 Refresh 會造成不同的效果 (不同數量的 Thread)
那麼要如何觀測系統是否有啟動 multi-thread 呢?以下是觀測的步驟,有興趣的人可以拿自己的系統試試看:
- 進入 Visual Studio 的 Debug Mode。或者先以 IIS/ASP.NET Development Server (lightweight 的那個)瀏覽到目標頁面後,將 Visual Studio 附加至該網頁的處理序上進行 debug。
- 在該 aspx 以及 BasePage、MasterPage、UserControl 等檔案的 code-behind 程式中的 Init / OnLoad 事件上設定中斷點。
- 觀察執行過程中「System.Threading.Thread.CurrentThread.ManagedThreadId」的值。
5 則留言:
TLS (ThreadLocalStorage) 行不通的話,改用 HttpContext.Current.Items 看看,也許是個除了 TLS 之外另一個選擇...
To chicken:
感謝你的建議!經過這個禮拜的測試,還有 MSDN 上的說明,看來 HttpContext.Current.Items 就是符合目前需求的 per-request data store ... 下週要進行壓力測試,看看擺脫 MSDTC 之後可以增進多少效能 :D
有用的話真是太好了 :D
我也碰到類似的問題,有自行開發的 ORM, 不過我們的問題麻煩了點,除了 ASP.NET 之外,也包裝成 COM 給 ASP 用... 在 ASP 端還沒找到妥善的 solution ...
印象中 ASP 都是以 Single Threaded Apartment (STA) 的模式運作的?(http://msdn.microsoft.com/en-us/library/aa479019.aspx)最近遇到一個 ASP.NET 網站在某台 VM 上出現「An error was encountered while calling OnStartPage in ASP compatibility mode.」的錯誤訊息,因為 .aspx 的 page 屬性中設定了「aspcompat=true」(為了讓 COM 元件正常運作),這錯誤好像很罕見...>_< Anyway,或許把 .NET Assembly 包裝成 Web Services 發布給 ASP 呼叫,可以減少一些問題。(把 .NET Assembly 包裝成 COM-visible,或者把 COM 作成 Intreop 元件感覺都是逼不得已的選擇...)
是的,逼不得已的選擇 :~
為了不讓 ASP / ASP.NET 有兩套做一樣事情的元件,最後就選擇 COM interop ...
張貼留言