簡體   English   中英

如何在C#中存儲每個線程的上下文數據?

[英]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控制流有關。 結束編輯

請參考msdn doc中的示例代碼

    // 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM