C#是否具有“ThreadLocal”模拟(对于数据成员)到“ThreadStatic”属性?

我发现“ThreadStatic”属性最近非常有用,但现在我想要一个“ThreadLocal”类型属性,它允许我在每个线程的基础上拥有非静态数据成员。

现在我知道这会产生一些非平凡的影响,但是:

C#/ .net中是否已经内置了这样的东西? 或者因为到目前为止它的答案是否定的(对于.net <4.0),是否有一个常用的实现?

我可以想出一个合理的方法来实现它,但只会使用已经存在的东西,如果可用的话。

如果它还不存在,那么它将实现我正在寻找的东西:

class Foo { [ThreadStatic] static Dictionary threadLocalValues = new Dictionary(); 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更有趣的function

我只限于.net 3.0和3.5 (项目已经/将很快转移到3.5)。

具体的用例是特定于线程的回调列表(使用虚构的[ThreadLocal]属性) a la:

 class NonSingletonSharedThing { [ThreadLocal] List callbacks; public void ThreadLocalRegisterCallback( Callback somecallback ) { callbacks.Add(somecallback); } public void ThreadLocalDoCallbacks(); { foreach( var callback in callbacks ) callback.invoke(); } } 

进入.NET 4.0!

如果你被困在3.5(或更早),你应该看一些函数 ,比如AllocateDataSlot应该做你想要的。

你应该考虑两次。 您实质上是在创建内存泄漏。 线程创建的每个对象都保持引用状态,不能进行垃圾回收。 直到线程结束。

如果您希望在每个线程的基础上存储唯一数据,您可以使用Thread.SetData。 请务必阅读优缺点http://msdn.microsoft.com/en-us/library/6sby1byh.aspx,因为这会影响性能。

考虑:

而不是试图给对象中的每个成员变量一个特定于线程的值,给每个线程赋予自己的对象实例。 – 将对象作为状态传递给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();
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. }
{ // (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. } 

我最终实现并测试了我最初建议的版本:

 public class ThreadLocal { [ThreadStatic] private static Dictionary _lookupTable; private Dictionary LookupTable { get { if ( _lookupTable == null) _lookupTable = new Dictionary(); 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; } } 

虽然我仍然不确定您的用例何时有意义(请参阅我对问题本身的评论),但我想提供一个工作示例,在我看来比线程本地存储更具可读性(无论是静态还是实例) 。 该示例使用的是.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 callback) { var id = Thread.CurrentThread.ManagedThreadId; lock (this._callbacks) { List> list; if (!this._callbacks.TryGetValue(id, out list)) { this._callbacks[id] = list = new List>(); } list.Add(callback); } } public void Execute() { var id = Thread.CurrentThread.ManagedThreadId; IEnumerable> 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>> _callbacks = new Dictionary>>(); } 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运行通知,然后从工作线程运行通知。 执行的回调被当前线程ID有效地过滤。 为了显示事情按预期工作,该示例添加了两个回调(总共4个)并再次从主线程和工作线程的上下文运行通知。

请注意,Notifier类是一个可以具有状态,多个实例等的常规实例(同样,根据您的问题的用例)。 示例不使用静态或线程静态或线程局部。

如果您能查看代码并告诉我,如果我误解了您要实现的目标,或者这样的技术是否能满足您的需求,我将不胜感激。

我不确定你是如何首先产生线程的,但是有一些方法可以为每个线程提供自己的线程本地存储,而不使用像你在问题中发布的代码那样的hackish变通方法。

 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 } 

这显然忽略了等待线程完成他们的工作,确保访问任何共享状态对所有工作线程都是线程安全的,但是你明白了。

虽然发布的解决方案看起来很优雅,但它会泄漏对象。 终结器 – LookupTable.Remove(key) – 仅在GC线程的上下文中运行,因此在创建另一个查找表时可能只会产生更多垃圾。

您需要从已访问ThreadLocal的每个线程的查找表中删除对象。 我能想到解决这个问题的唯一优雅方法是通过弱键控字典 – 一种奇怪的缺少c#的数据结构。