开发者

Does C# have a "ThreadLocal" analog (for data members) to the "ThreadStatic" attribute?

开发者 https://www.devze.com 2022-12-18 15:13 出处:网络
I\'ve found the \"ThreadStatic\" attribute to be extremely useful recently, but makes me now want a \"ThreadLocal\" type attribute that lets me have non-static data members on a per-thread basis.

I've found the "ThreadStatic" attribute to be extremely useful recently, but makes me now want a "ThreadLocal" type attribute that lets me have non-static data members on a per-thread basis.

Now I'm aware that this would have some non-trivial implications, but:

Does such a thing exist already built into C#/.net? or since it appears so far that the answer to this is no (for .net < 4.0), is there a commonly used implementation out there?

I can think of a reasonable way to implement it myself, but would just use something that already existed if it were available.

Straw Man example that would implement what I'm looking for if it doesn't already exist:

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; }
    }
}

Please forgive any C# ignorance. 开发者_如何学运维I'm a C++ developer that has only recently been getting into the more interesting features of C# and .net

I'm limited to .net 3.0 and maybe 3.5 (project has/will soon move to 3.5).

Specific use-case is callback lists that are thread specific (using imaginary [ThreadLocal] attribute) 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();  
     }
}


Enter .NET 4.0!

If you're stuck in 3.5 (or earlier), there are some functions you should look at, like AllocateDataSlot which should do what you want.


You should think about this twice. You are essentially creating a memory leak. Every object created by the thread stays referenced and can't be garbage collected. Until the thread ends.


If you looking to store unique data on a per thread basis you could use Thread.SetData. Be sure to read up on the pros and cons http://msdn.microsoft.com/en-us/library/6sby1byh.aspx as this has performance implications.


Consider:

Rather than try to give each member variable in an object a thread-specific value, give each thread its own object instance. -- pass the object to the threadstart as state, or make the threadstart method a member of the object that the thread will "own", and create a new instance for each thread that you spawn.

Edit (in response to Catskul's remark. Here's an example of encapsulating the 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();

Now here's option 2 (pass the struct as state)


 {
 // (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.
  }


I ended up implementing and testing a version of what I had originally suggested:

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;
    }
}


Although I am still not sure about when your use case would make sense (see my comment on the question itself), I would like to contribute a working example that is in my opinion more readable than thread-local storage (whether static or instance). The example is using .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();
            }
        }
    }
}

When you compile and run the above program, you should get output like this: alt text http://img695.imageshack.us/img695/991/threadlocal.png

Based on your use-case I assume this is what you're trying to achieve. The example first adds two callbacks from two different contexts, main and worker threads. Then the example runs notification first from main and then from worker threads. The callbacks that are executed are effectively filtered by current thread ID. Just to show things are working as expected, the example adds two more callbacks (for a total of 4) and again runs the notification from the context of main and worker threads.

Note that Notifier class is a regular instance that can have state, multiple instances, etc (again, as per your question's use-case). No static or thread-static or thread-local is used by the example.

I would appreciate if you could look at the code and let me know if I misunderstood what you're trying to achieve or if a technique like this would meet your needs.


I'm not sure how you're spawning your threads in the first place, but there are ways to give each thread its own thread-local storage, without using hackish workarounds like the code you posted in your question.

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
}

This obviously ignores waiting on the threads to finish their work, making sure accesses to any shared state is thread-safe across all the worker threads, but you get the idea.


Whilst the posted solution looks elegant, it leaks objects. The finalizer - LookupTable.Remove(key) - is run only in the context of the GC thread so is likely only creating more garbage in creating another lookup table.

You need to remove object from the lookup table of every thread that has accessed the ThreadLocal. The only elegant way I can think of solving this is via a weak keyed dictionary - a data structure which is strangely lacking from c#.

0

精彩评论

暂无评论...
验证码 换一张
取 消