2010-01-29 52 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

相关问题