我发现“ThreadStatic”属性最近非常有用,但现在我想要一个让我拥有的“ThreadLocal”类型属性每个线程的非静态数据成员。
现在我知道这会产生一些非常重要的影响,但是:
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 3.0,可能只有3.5 (项目已经/很快会移至3.5)。
特定用例是特定于线程的回调列表(使用虚构的[ThreadLocal]属性) a la:
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 :(得分:22)
如果你被困在3.5(或更早),你应该看some functions,例如AllocateDataSlot
应该做你想做的事。
答案 1 :(得分:5)
你应该考虑两次。您实质上是在创建内存泄漏。 线程创建的每个对象都保持引用状态,不能进行垃圾回收。直到线程结束。
答案 2 :(得分:4)
如果您希望在每个线程的基础上存储唯一数据,可以使用Thread.SetData。请务必阅读优缺点http://msdn.microsoft.com/en-us/library/6sby1byh.aspx,因为这会影响性能。
答案 3 :(得分:4)
考虑:
不是试图给对象中的每个成员变量赋予特定于线程的值,而是为每个线程赋予自己的对象实例。 - 将对象作为状态传递给threadstart,或者使threadstart方法成为线程将“拥有”的对象的成员,并为您生成的每个线程创建一个新实例。
修改的 (回应Catskul的评论。 这是封装struct
的示例
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.
}
答案 4 :(得分: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;
}
}
答案 5 :(得分:1)
虽然我仍然不确定您的用例何时有意义(请参阅我对该问题本身的评论),但我想提供一个工作示例,在我看来比线程本地存储更具可读性(无论是静态的)或实例)。该示例使用的是.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运行通知,然后从工作线程运行通知。执行的回调被当前线程ID有效过滤。为了显示事情按预期工作,该示例添加了两个回调(总共4个)并再次从主线程和工作线程的上下文运行通知。
请注意,Notifier类是一个可以具有状态,多个实例等的常规实例(同样,根据您的问题的用例)。示例中没有使用静态或线程静态或线程局部。
如果您能查看代码并告诉我是否我误解了您要实现的目标,或者这样的技术是否符合您的需求,我将不胜感激。
答案 6 :(得分:0)
我不确定你是如何首先产生你的线程的,但有一些方法可以为每个线程提供自己的线程本地存储,而不使用像你在问题中发布的代码那样的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
}
这显然忽略了等待线程完成他们的工作,确保对所有工作线程的任何共享状态的访问都是线程安全的,但是你明白了。
答案 7 :(得分:0)
虽然发布的解决方案看起来很优雅,但它会泄漏对象。终结器 - LookupTable.Remove(key) - 仅在GC线程的上下文中运行,因此在创建另一个查找表时可能只会产生更多垃圾。
您需要从已访问ThreadLocal的每个线程的查找表中删除对象。我能想到解决这个问题的唯一优雅方法是通过一个弱键控字典 - 一个奇怪的缺少c#的数据结构。