在 .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」的值。