[英]How to store per thread contextual data in c#?
我的具體需求是存儲遠程IP,以便可以在記錄日志時將其打印出來,而無需在每個方法調用中明確傳遞它。 一種基於線程的環境變量范圍。
我想到了每線程Singleton,以便記錄器可以訪問它。 有沒有更好/更安全的方法?
使它與異步一起工作是一個加號。
我理想的API應該如下所示:
using (new LogScope("key1", "value1"))
{
Call1();
}
using (new LogScope("key1", "value1"))
{
Call1();
}
void Call1()
{
using (new LogScope("key2", "value2"))
{
Call2(); // key1:value1 key2:value2
using (new LogScope("key1", "value3"))
{
Call2(); // key1:value3 key2:value2
}
}
using (new LogScope("key1", "value3"))
{
Call2(); // key1:value3
}
}
void Call2()
{
foreach (var kv in LogScope.Context) Console.Write("'{0}':'{1}' ");
Console.WriteLine();
}
a)如果您想使用每個線程單例,則絕對不應編寫自己的單例,因為有現成的解決方案。 主要是所有現代DI框架都支持實例化選項,例如單例和每個線程的單例。 只是Ninject的一個例子:
kernel.Bind(typeof (IAnyThing)).To(typeof (AnyThing)).InThreadScope();
或者如果您不想使用/實現接口:
kernel.Bind(typeof (AnyThing)).To(typeof (AnyThing)).InThreadScope();
在該綁定之后,無論您向DI容器請求什么,AnyThing的實例都將獲得每個線程單例。 任何事情可能實際上是任何事情:說一個POCO。
編輯:如果您想要任何自定義范圍,則只需實現您的范圍定義,剩下的容器其余部分將在此頁面中查找自定義
b)您可以使用ThreadLocal<T>
。
編輯:在建議ThreadLocal之前,您在問題中提到的有關異步期望的注釋。 是的,ThreadLocal與異步不“兼容”。
但這不是問題的根源: 線程相似性與大多數並發編程設計模式(CDP)不“兼容”,因為許多CDP使用線程“非相似性”的概念,例如池,序列化到消息隊列,因此,將線程親和性作為期望,而將異步工作作為期望,似乎並不是一個好主意,應該刪除或替換它。 請注意,這與async關鍵字無關,而與async控制流有關。 結束編輯
// Demonstrates:
// ThreadLocal(T) constructor
// ThreadLocal(T).Value
// One usage of ThreadLocal(T)
static void Main()
{
// Thread-Local variable that yields a name for a thread
ThreadLocal<string> ThreadName = new ThreadLocal<string>(() =>
{
return "Thread" + Thread.CurrentThread.ManagedThreadId;
});
// Action that prints out ThreadName for the current thread
Action action = () =>
{
// If ThreadName.IsValueCreated is true, it means that we are not the
// first action to run on this thread.
bool repeat = ThreadName.IsValueCreated;
Console.WriteLine("ThreadName = {0} {1}", ThreadName.Value, repeat ? "(repeat)" : "");
};
// Launch eight of them. On 4 cores or less, you should see some repeat ThreadNames
Parallel.Invoke(action, action, action, action, action, action, action, action);
// Dispose when you are done
ThreadName.Dispose();
}
不是最理想的實現。 但是它應該可以滿足您的需求。 並且它至少應該展示一些您可以重用的原理。
public class LogScope : IDisposable
{
private static readonly ThreadLocal<Stack<Dictionary<string, string>>> currentDictionary =
new ThreadLocal<Stack<Dictionary<string, string>>>(() => new Stack<Dictionary<string, string>>());
public LogScope(string key, string value)
{
var stack = currentDictionary.Value;
Dictionary<string, string> newDictionary = null;
if (stack.Count == 0)
{
newDictionary = new Dictionary<string, string>();
}
else
{
newDictionary = new Dictionary<string, string>(stack.Peek());
}
newDictionary[key] = value;
stack.Push(newDictionary);
}
public void Dispose()
{
currentDictionary.Value.Pop();
}
public static IEnumerable<KeyValuePair<string, string>> Context
{
get
{
var stack = currentDictionary.Value;
if (stack.Count == 0)
{
return Enumerable.Empty<KeyValuePair<string, string>>();
}
else
{
return stack.Peek();
}
}
}
}
編輯:我要說明,在我的執行LogScope
,實例LogScope
才會正常,如果它被實例化,並設置在同一線程上運行。 如果在一個線程中創建一個LogScope
,然后將其放置在另一個線程中,則結果是不確定的。 同樣,當LogScope
實例生效時,其鍵/值將僅在該線程中可見。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.