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

时间:2010-01-29 00:08:51

标签: c# .net multithreading attributes

我发现“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();  
     }
}

8 个答案:

答案 0 :(得分:22)

Enter .NET 4.0!

如果你被困在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#的数据结构。