Есть ли в C # аналог ThreadLocal (для элементов данных) атрибуту ThreadStatic? - PullRequest
22 голосов
/ 29 января 2010

Я обнаружил, что атрибут "ThreadStatic" в последнее время чрезвычайно полезен, но теперь мне нужен атрибут типа "ThreadLocal" , который позволяет мне не статические элементы данных для каждого потока.

Теперь я знаю, что это будет иметь некоторые нетривиальные последствия, но:

Существует ли такая вещь, уже встроенная в C # /. Net? или, поскольку кажется, что ответ на этот вопрос - нет (для .net <4.0), обычно используется реализация там? </em>

Я могу придумать разумный способ реализовать это сам, но просто использовал бы то, что уже существовало, если бы оно было доступно.

Straw Man пример, который бы реализовал то, что я ищу, если бы его еще не было:

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 ]

22 голосов
/ 29 января 2010

Введите .NET 4.0!

Если вы застряли в 3.5 (или ранее), есть некоторые функции , на которые вы должны смотреть, например AllocateDataSlot, которые должны делать то, что вы хотите.

5 голосов
/ 29 января 2010

Тебе стоит подумать об этом дважды. Вы по сути создаете утечку памяти. Каждый объект, созданный потоком, остается ссылочным и не может быть собран мусором. Пока не закончится нить.

4 голосов
/ 04 февраля 2010

Рассмотрим:

Вместо того, чтобы пытаться дать каждой переменной-члену в объекте специфичное для потока значение, присвойте каждому потоку собственный экземпляр объекта. - передать объект в запуск потока как состояние или сделать метод запуска потока членом объекта, которому будет принадлежать поток, и создать новый экземпляр для каждого потока, который вы породите.

Редактировать (в ответ на замечание Катскул. Вот пример инкапсуляции структуры


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 голосов
/ 29 января 2010

Если вы хотите хранить уникальные данные для каждого потока, вы можете использовать Thread.SetData. Обязательно ознакомьтесь с плюсами и минусами http://msdn.microsoft.com/en-us/library/6sby1byh.aspx, поскольку это влияет на производительность.

3 голосов
/ 09 апреля 2010

В итоге я реализовал и протестировал версию, которую я изначально предложил:

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;
    }
}
1 голос
/ 04 февраля 2010

Хотя я до сих пор не уверен, когда ваш вариант использования будет иметь смысл (см. Мой комментарий к самому вопросу), я хотел бы представить рабочий пример, который, на мой взгляд, более читабелен, чем локальное хранилище потока (будь то статическое или экземпляр). В примере используется .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();
            }
        }
    }
}

Когда вы компилируете и запускаете вышеуказанную программу, вы должны получить вывод, подобный следующему: альтернативный текст http://img695.imageshack.us/img695/991/threadlocal.png

Исходя из вашего варианта использования, я предполагаю, что это то, чего вы пытаетесь достичь. Вначале в примере добавляются два обратных вызова из двух разных контекстов, основного и рабочего потоков. Затем пример запускает уведомление сначала из основного, а затем из рабочих потоков. Выполненные обратные вызовы эффективно фильтруются по текущему идентификатору потока. Просто чтобы показать, что все работает как положено, пример добавляет еще два обратных вызова (всего 4) и снова запускает уведомление из контекста основного и рабочего потоков.

Обратите внимание, что класс Notifier - это обычный экземпляр, который может иметь состояние, несколько экземпляров и т. Д. (Опять же, согласно сценарию использования вашего вопроса). В этом примере не используются статические, потоковые или локальные потоки.

Буду признателен, если вы посмотрите на код и дадите мне знать, если я неправильно понял, чего вы пытаетесь достичь, или если подобная техника будет соответствовать вашим потребностям.

0 голосов
/ 06 августа 2010

Хотя опубликованное решение выглядит элегантно, оно пропускает объекты. Финализатор - LookupTable.Remove (key) - запускается только в контексте потока GC, поэтому, скорее всего, только создает больше мусора при создании другой таблицы поиска.

Вам необходимо удалить объект из таблицы поиска каждого потока, который получил доступ к ThreadLocal. Единственный элегантный способ решить эту проблему - использовать словарь со слабым ключом - структуру данных, которой странным образом не хватает в c #.

0 голосов
/ 30 января 2010

Вначале я не уверен, как вы создаете свои потоки, но есть способы дать каждому потоку свое собственное локальное хранилище, без использования хакерских обходных путей, таких как код, который вы опубликовали в своем вопросе.

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
}

Это, очевидно, игнорирует ожидание того, что потоки завершат свою работу, убедившись, что доступ к любому общему состоянию является потокобезопасным во всех рабочих потоках, но вы поняли идею.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...