爬了幾篇網路上的文章,實在是有看沒有懂,所以往下去追 log4net 的原始碼,再試著寫程式去實驗才明白這兩者之間的差異,顧名思義 LogicalThreadContext 與 ThreadContext 在選擇使用上跟 Thread 息息相關。
情況是這樣的,我們既有的系統把日誌收錄在資料庫內,要做這樣的事使用 log4net 搭配 AdoNetAppender 是蛋糕一塊,但是在用 SpecFlow + SpecRun 跑整合測試的時候我卻收到了這樣的錯誤訊息 System.Runtime.Serialization.SerializationException: 找不到組件 'log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a'。
,在解決問題的過程中,明白了 LogicalThreadContext 與 ThreadContext 的區別。
如果我們去搜尋 log4net 關於 AdoNetAppender 的範例,客製欄位的部分大都會要我們自訂 %property{XXX}
,指定欄位值的部分會出現 ThreadContext.Properties["XXX"]
及 LogicalThreadContext.Properties["XXX"]
兩種範例,一般的程式設計師 Copy & Paste 會 Work 之後,也就不會再去探究這兩者的差別。
ThreadContext
探究 LogicalThreadContext 與 ThreadContext 的差別我們直接從原始碼下手,我們打開 ThreadContext 的原始碼來看,其實沒有幾行大部分是註解,其中關鍵點在 Properties
這個屬性值,可以看到它的型別是 ThreadContextProperties
,再往下追 ThreadContextProperties 的原始碼,追到最後會發現 ThreadContext.Properties 實際上是存放在 System.Threading.Thread 當前執行緒的定義域內,並使用 GetData() 及 SetData() 這兩個方法來存取值。
我們可以說 ThreadContext 將個別 Thread 的 Properties 存放在當前執行緒的定義域裡面,不同 Thread 之間無法共享 Properties,那麼就寫個程式來實驗一下。
我使用 ConsoleAppender 將我的日誌訊息印在畫面上,並增加一個 customprop
自訂屬性。
跑一個迴圈起 10 個 Thread 分別各自指定 customprop 的值,可以看到每個 Thread 的 customprop 值是不一樣的。
但是...從 .NET Framework 4.0 開始我們在處理多工作業上多了一個選擇 - Task,而我自己也是以 Task 為多工作業的優先選擇,可是不同的 Task 不一定是相異的 Thread,也有可能是相同的 Thread,這讓個別 Thread 之間訊息上下文的生命週期就變得無法控制。
LogicalThreadContext
針對個別 Thread 之間訊息上下文的生命週期無法控制的問題,LogicalThreadContext 提供了一個邏輯上 ThreadContext,可以幫助我們解決這樣的問題。
這怎麼辦到的? 我們來去看 LogicalThreadContext 的原始碼,一樣沒幾行大部分是註解,一樣也有一個 Properties 的屬性,但是它的型別變成 LogicalThreadContextProperties
,我們就往下去追 LogicalThreadContextProperties 的原始碼,追到最後會發現 LogicalThreadContext.Properties 是存放在一個 CallContext 的儲存區裡面。
CallContext 是什麼? 根據 MSDN 的解釋它是特製化的集合物件類似方法呼叫的執行緒區域儲存區,並提供對每個邏輯執行緒都是唯一的資料位置,這個知道就好了,因為 log4net 已經將其重新封裝過,我們直接操作 LogicalThreadContextProperties 就行了。
有一個地方需要特別注意一下,LogicalThreadContext.Properties 在指定值的時候,擺放位置相當重要,如果我在主執行緒也指定自訂屬性 customprop 的值會怎樣?
它是會往後續堆疊的方法傳遞的,即使那個方法是另外一個 Thread 也是一樣。