【茶包射手日記】ASP.NET寫Log檔的多執行緒問題
小熊子提供日常生活茶包一枚,某機上盒設備偶然在液晶電視彈出以下訊息:

看起來是ASP.NET程式錯誤,雖然接到電視時畫面周圍被截,但仍看得出是同時寫入Log檔的多條執行緒在打架所致。頓時有熟悉的感覺: 啊! 這茶包我也嚐過。
由錯誤訊息所暗示的片段程式碼(錯誤示範! 正式台web.config應啟用customErrors,避免錯誤細節及程式碼被閒雜人等掌握),推敲原來的程式碼如下:
public static void WriteLog(string sErrMsg)
{
StreamWriter sw = new StreamWriter(pathName + sErrorTime + extName, true);
sw.WriteLine(sErrMsg);
sw.Close();
}
這種寫法原則上OK,也多半可順利通過QA團隊的測試,但等到實際上線與成千上萬使用者一起歡樂時,才會顯露其"非Thread-Safe"的問題。以現在的電腦等級,開檔、寫資料、關檔的過程或許花不到千分之一秒,但IIS是以多執行緒方式執行使用者要求的網頁,很難保證不會那麼湊巧剛好有兩個網頁分秒不差同時要開檔案寫Log,一但發生,手腳較慢的一方就會因為其他程式正在寫入同一檔案,檔案被鎖定中而導致錯誤。
要做到Thread-Safe,最簡單的方法就是--大家排隊,一次只讓一條執行緒(在IIS裡就等於一個ASP.NET網頁的Request)執行WriteLog(),這用C#的lock指令就可輕鬆實現。另外,由於StreamWriter涉及Unmamaged Resource,用using包起來確保程式一定會釋放檔案資源是更好的做法。
static object lockMe = new object();
public static void WriteLog(string sErrMsg)
{
lock (lockMe)
{
using (StreamWriter sw =
new StreamWriter(pathName + sErrorTime + extName, true))
{
sw.WriteLine(sErrMsg);
sw.Close();
}
}
}
最後,雖然能由錯誤訊息偷窺別人的程式碼修練武功很有趣,但還是要強烈呼籲所有寫ASP.NET的朋友,程式上線時記得要將web.config的customErrors設成"On"或"RemoteOnly",出錯直接顯示錯誤訊息及程式碼雖然偶爾會引來雞婆好心的程式魔人幫忙射茶包給建議,但見縫插針有洞就鑽的壞人更多,小心錯誤訊息引狼入室。