2010-01-29 19 views
22

我發現「ThreadStatic」屬性是極其有用的最近的,但現在讓我想「ThreadLocal的」類型的屬性,讓我有非靜態數據成員在每個線程的基礎。C#是否具有「ThreadLocal」模擬(用於數據成員)到「ThreadStatic」屬性?

現在我知道,這將有一些不平凡的意義,但:

難道這樣的事情存在已內置到C#/。NET?或者因爲它看起來到目前爲止的答案是否定的(對於.net < 4.0),那裏是否有常用的實現?

我可以想出一個合理的方法來實現它自己,但只是使用一些已經存在的東西,如果它是可用的。

將實現什麼我要找的,如果它已經不稻草人例如存在:

class Foo 
{ 
    [ThreadStatic] 
    static Dictionary<Object,int> threadLocalValues = new Dictionary<Object,int>(); 
    int defaultValue = 0; 

    int ThreadLocalMember 
    { 
     get 
     { 
       int value = defaultValue; 
       if(! threadLocalValues.TryGetValue(this, out value)) 
       { 
       threadLocalValues[this] = value; 
       } 
       return value; 
     } 
     set { threadLocalValues[this] = value; } 
    } 
} 

請原諒任何C#無知。我是一位C++開發人員,最近纔開始研究C#和.NET的更有趣的功能.net

我僅限於.net 3.0,也許3.5(項目已經/很快將移動到3.5)。

具體使用情況是,在線程特定(使用虛構[ThreadLocal的]屬性)回調列表一拉:

class NonSingletonSharedThing 
{ 
    [ThreadLocal] List<Callback> callbacks; 

    public void ThreadLocalRegisterCallback(Callback somecallback) 
    {  
     callbacks.Add(somecallback);  
    } 

    public void ThreadLocalDoCallbacks(); 
    {  
     foreach(var callback in callbacks) 
      callback.invoke(); 
    } 
} 
+0

我看不出有什麼不同。如果數據是每個線程,爲什麼不是ThreadStatic你需要什麼?換句話說,當地對什麼? – 2010-01-29 15:29:41

+0

這就是爲什麼我提供了這個例子。我正在爲每個類尋找每個非靜態數據成員。 ThreadStatic僅適用於靜態數據成員。 – Catskul 2010-01-29 15:49:10

+5

我很好奇,這是什麼用例?如果對象綁定到局部變量,那麼它和它的所有成員實際上是線程局部的。如果對象綁定到一個全局變量,全局的ThreadStatic將使它(和它的成員)成爲本地線程。該對象需要共享線程本地存儲纔有用,它是如何共享的,以便選定的成員有效地成爲線程本地? – 2010-02-02 01:16:41

回答

22

Enter .NET 4.0!

如果你停留在3.5(或更早版本),有some functions你應該看看,像AllocateDataSlot應該做你想做的。

+1

絕對仍然在3.0或3.5; 4.0目前不適合我。否則,4.0東西看起來可能是我在找的東西。可悲的是,它可能意味着什麼都不存在.net <4.0:/ – Catskul 2010-01-29 16:40:37

+9

你總是可以使用Reflector從.NET4反向工程ThreadLocal 實現,將它包含在你的項目中,並且當你最終轉移到.NET4時刪除您的逆向工程實現(其他所有內容都將保持不變)。當從.NET2移植到.NET 3.5時,我在TimeZoneInfo中成功使用了該技術。 – 2010-02-04 14:56:38

+0

@Milan:這看起來像最成功的解決方案,因爲它看起來很清楚,我正在尋找的東西不存在.net 3.x – Catskul 2010-02-04 22:13:56

4

如果您希望以每個線程爲基礎存儲唯一數據,則可以使用Thread.SetData。請務必閱讀正面和反面http://msdn.microsoft.com/en-us/library/6sby1byh.aspx,因爲這具有性能影響。

+0

謝謝。這很有趣,但不止一點尷尬。我很可能會在使用之前使用我的稻草人示例。 – Catskul 2010-01-29 15:48:11

5

你應該考慮兩次。你實質上是在創建一個內存泄漏。 每個線程創建的對象都保持引用狀態,不能被垃圾收集。直到線程結束。

+0

好點,雖然它可以被解決/照顧。所有更多的理由使用內置的功能,如果它存在,因爲它會照顧到這一點。 – Catskul 2010-01-29 15:25:21

0

我不確定你是如何產生你的線程的,但是有辦法讓每個線程都擁有自己的線程本地存儲,而不需要使用像你在問題中發佈的代碼那樣的黑客替代方法。

public void SpawnSomeThreads(int threads) 
{ 
    for (int i = 0; i < threads; i++) 
    { 
     Thread t = new Thread(WorkerThread); 

     WorkerThreadContext context = new WorkerThreadContext 
     { 
      // whatever data the thread needs passed into it 
     }; 

     t.Start(context); 
    } 
} 

private class WorkerThreadContext 
{ 
    public string Data { get; set; } 
    public int OtherData { get; set; } 
} 

private void WorkerThread(object parameter) 
{ 
    WorkerThreadContext context = (WorkerThreadContext) parameter; 

    // do work here 
} 

這顯然忽略了等待的線程完成自己的工作,確保訪問任何共享狀態是線程安全的所有工作線程,但你的想法。

+0

我並不擔心每個線程都有自己的線程本地存儲。線程本地存儲容易由c#提供。我很有趣給對象線程本地成員。 – Catskul 2010-02-01 17:40:30

4

考慮:

,而不是試圖給每個成員變量在對象的線程特定值,爲每個線程提供自己的對象實例。 - 將對象作爲狀態傳遞給threadstart,或者使threadstart方法成爲線程將擁有的對象的成員,併爲每個產生的線程創建一個新實例。

編輯 (響應Catskul此言。 這裏的封裝結構現在

 

public class TheStructWorkerClass 
{ 
    private StructData TheStruct; 
    public TheStructWorkerClass(StructData yourStruct) 
    { 
    this.TheStruct = yourStruct; 
    } 

    public void ExecuteAsync() 
    { 
    System.Threading.ThreadPool.QueueUserWorkItem(this.TheWorkerMethod); 
    } 
    private void TheWorkerMethod(object state) 
    { 
    // your processing logic here 
    // you can access your structure as this.TheStruct; 
    // only this thread has access to the struct (as long as you don't pass the struct 
    // to another worker class) 
    } 
} 

// now hte code that launches the async process does this: 
    var worker = new TheStructWorkerClass(yourStruct); 
    worker.ExecuteAsync(); 
 

的例子這裏的選項2(一樣傳遞結構狀態)

 

{ 
// (from somewhere in your existing code 
    System.Threading.Threadpool.QueueUserWorkItem(this.TheWorker, myStruct); 
} 

    private void TheWorker(object state) 
    { 
    StructData yourStruct = (StructData)state; 
    // now do stuff with your struct 
    // works fine as long as you never pass the same instance of your struct to 2 different threads. 
    } 
 
+0

+1這是正確的做法。 – 2010-02-04 20:26:24

+1

我並不擁有圍繞它的結構,所以它不是按照您的建議重新構建它的選項。即使我這樣做了,但對於我的具體情況來說,以這種方式進行重組並不合適。 – Catskul 2010-02-04 21:20:59

+0

@Catskul:如果你不能重構那個結構體,你可以封裝它,或者把它作爲狀態傳遞給線程。如果您將它作爲狀態傳遞,那麼您需要爲每個線程創建1個結構(但不必更改關於該結構的任何內容)。爲了封裝,你需要定義一個擁有你的結構並且包含threadstart的類。該線程將運行該類的實例並訪問該類的結構專用副本。我會用一個例子編輯我的回覆。 – JMarsch 2010-02-04 21:30:30

1

雖然我我仍然不確定你的用例何時有意義(請參閱我對該問題本身的評論),我想提供一個在我看來比t更可讀的工作示例hread本地存儲(無論是靜態還是實例)。這個例子是使用.NET 3.5:當你編譯並運行上述程序

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Threading; 
using System.Linq; 

namespace SimulatedThreadLocal 
{ 
    public sealed class Notifier 
    { 
     public void Register(Func<string> callback) 
     { 
      var id = Thread.CurrentThread.ManagedThreadId; 
      lock (this._callbacks) 
      { 
       List<Func<string>> list; 
       if (!this._callbacks.TryGetValue(id, out list)) 
       { 
        this._callbacks[id] = list = new List<Func<string>>(); 
       } 
       list.Add(callback); 
      } 
     } 

     public void Execute() 
     { 
      var id = Thread.CurrentThread.ManagedThreadId; 
      IEnumerable<Func<string>> threadCallbacks; 
      string status; 
      lock (this._callbacks) 
      { 
       status = string.Format("Notifier has callbacks from {0} threads, total {1} callbacks{2}Executing on thread {3}", 
        this._callbacks.Count, 
        this._callbacks.SelectMany(d => d.Value).Count(), 
        Environment.NewLine, 
        Thread.CurrentThread.ManagedThreadId); 
       threadCallbacks = this._callbacks[id]; // we can use the original collection, as only this thread can add to it and we're not going to be adding right now 
      } 

      var b = new StringBuilder(); 
      foreach (var callback in threadCallbacks) 
      { 
       b.AppendLine(callback()); 
      } 

      Console.ForegroundColor = ConsoleColor.DarkYellow; 
      Console.WriteLine(status); 
      Console.ForegroundColor = ConsoleColor.Green; 
      Console.WriteLine(b.ToString()); 
     } 

     private readonly Dictionary<int, List<Func<string>>> _callbacks = new Dictionary<int, List<Func<string>>>(); 
    } 

    public static class Program 
    { 
     public static void Main(string[] args) 
     { 
      try 
      { 
       var notifier = new Notifier(); 
       var syncMainThread = new ManualResetEvent(false); 
       var syncWorkerThread = new ManualResetEvent(false); 

       ThreadPool.QueueUserWorkItem(delegate // will create closure to see notifier and sync* events 
       { 
        notifier.Register(() => string.Format("Worker thread callback A (thread ID = {0})", Thread.CurrentThread.ManagedThreadId)); 
        syncMainThread.Set(); 
        syncWorkerThread.WaitOne(); // wait for main thread to execute notifications in its context 

        syncWorkerThread.Reset(); 
        notifier.Execute(); 
        notifier.Register(() => string.Format("Worker thread callback B (thread ID = {0})", Thread.CurrentThread.ManagedThreadId)); 
        syncMainThread.Set(); 
        syncWorkerThread.WaitOne(); // wait for main thread to execute notifications in its context 

        syncWorkerThread.Reset(); 
        notifier.Execute(); 
        syncMainThread.Set(); 
       }); 

       notifier.Register(() => string.Format("Main thread callback A (thread ID = {0})", Thread.CurrentThread.ManagedThreadId)); 
       syncMainThread.WaitOne(); // wait for worker thread to add its notification 

       syncMainThread.Reset(); 
       notifier.Execute(); 
       syncWorkerThread.Set(); 
       syncMainThread.WaitOne(); // wait for worker thread to execute notifications in its context 

       syncMainThread.Reset(); 
       notifier.Register(() => string.Format("Main thread callback B (thread ID = {0})", Thread.CurrentThread.ManagedThreadId)); 
       notifier.Execute(); 
       syncWorkerThread.Set(); 
       syncMainThread.WaitOne(); // wait for worker thread to execute notifications in its context 

       syncMainThread.Reset(); 
      } 
      finally 
      { 
       Console.ResetColor(); 
      } 
     } 
    } 
} 

,你應該得到的輸出是這樣的: alt text http://img695.imageshack.us/img695/991/threadlocal.png

根據您的使用情況我想這就是你」重新努力實現。該示例首先添加來自兩個不同上下文的主線程和輔助線程的兩個回調。然後該示例首先從main運行通知,然後從worker線程運行通知。執行的回調會被當前線程ID有效過濾。爲了顯示事情按預期工作,該示例添加了兩個回調(總共4個回調),並再次從主線程和輔助線程的上下文運行通知。

請注意,通告程序類是一個常規實例,可以有狀態,多個實例等(再次,根據您的問題的用例)。示例中不使用靜態或線程靜態或線程局部。

如果你能看看代碼並讓我知道,如果我誤解了你想要達到的目標,或者像這樣的技術能夠滿足你的需求,我將不勝感激。

+0

感謝您爲此答案付出的努力。但是,如果不進行大量的細節,這是不會幫助我的。我真的只關心每個線程的非靜態數據成員在.net 3.x中是否可用(以每個線程靜態的方式)。 – Catskul 2010-02-04 22:12:35

3

我結束了實施和測試版本的什麼我原本建議:

public class ThreadLocal<T> 
{ 
    [ThreadStatic] private static Dictionary<object, T> _lookupTable; 

    private Dictionary<object, T> LookupTable 
    { 
     get 
     { 
      if (_lookupTable == null) 
       _lookupTable = new Dictionary<object, T>(); 

      return _lookupTable; 
     } 
    } 


    private object key = new object(); //lazy hash key creation handles replacement 
    private T originalValue; 

    public ThreadLocal(T value) 
    { 
     originalValue = value; 
    } 

    ~ThreadLocal() 
    { 
     LookupTable.Remove(key); 
    } 

    public void Set(T value) 
    { 
     LookupTable[key] = value; 
    } 

    public T Get() 
    { 
     T returnValue = default(T); 
     if (!LookupTable.TryGetValue(key, out returnValue)) 
      Set(originalValue); 

     return returnValue; 
    } 
} 
+0

我認爲當Set()之前沒有被調用(返回'default(T)'而不是'originalValue'?)時,Get()中存在一個bug。此外,規範的IDisposable支持不需要比聲明'〜ThreadLocal'更多的工作? – sehe 2012-12-17 16:55:07

0

雖然發佈解決方案看起來優雅,它泄漏的對象。終結器 - LookupTable.Remove(key) - 僅在GC線程的上下文中運行,因此可能只會在創建另一個查找表時創建更多垃圾。

您需要從訪問ThreadLocal的每個線程的查找表中刪除對象。我能想到解決這個問題的唯一方法就是通過一個弱鍵字典 - 一種奇怪的c#數據結構。

+0

這可能會更好地用作您所指的具體答案的評論,而不是作爲答案本身。假設你指的是我提出的實施方案,我是否正確? – Catskul 2010-08-17 22:49:39

+0

對不起,我還沒有能夠添加評論給答案(低代表)。我指的是你的建議實施。您正在使用終結器來移除LookupTable鍵,這一眼看起來似乎很好,但考慮一下GC在其自己的線程中運行的情況。運行時發生的所有事情是它創建一個新的LookupTable,並試圖從其中刪除密鑰,從而無法實現。它不泄漏對象的唯一情況是GC在唯一訪問ThreadLocal的線程的上下文中運行的地方 - 這是一種不太可能發生的情況。 – Mania 2010-09-08 04:35:43

相關問題