Ответ 1
Если вы застряли в 3.5 (или ранее), есть некоторые функции, на которые вы должны смотреть, например AllocateDataSlot
, которые должны делать что вы хотите.
Я обнаружил, что атрибут "ThreadStatic" чрезвычайно полезен в последнее время, но теперь мне нужен атрибут типа "ThreadLocal", который позволяет мне иметь нестатические элементы данных для каждой строки.
Теперь я знаю, что это будет иметь некоторые нетривиальные последствия, но:
Существует ли такая вещь, уже встроенная в С#/. net?, или поскольку она пока показывается, что ответ на это нет (для .net < 4.0), есть ли обычно используемое реализация там?
Я могу придумать разумный способ реализовать его сам, но просто использовал бы что-то, что уже существовало, если оно было доступно.
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; }
}
}
Прошу простить любое незнание С#. Я разработчик С++, который только недавно попадал в более интересные функции С# и .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();
}
}
Если вы застряли в 3.5 (или ранее), есть некоторые функции, на которые вы должны смотреть, например AllocateDataSlot
, которые должны делать что вы хотите.
Вы должны подумать об этом дважды. Вы по существу создаете утечку памяти. Каждый объект, созданный потоком, ссылается и не может быть собрано мусором. Пока нить не закончится.
Если вы хотите хранить уникальные данные для каждого потока, вы можете использовать Thread.SetData. Обязательно прочитайте "за" и "против" http://msdn.microsoft.com/en-us/library/6sby1byh.aspx, так как это имеет последствия для производительности.
Рассмотрим:
Вместо того, чтобы пытаться дать каждой переменной-члену в объекте значение, зависящее от потока, дать каждому потоку свой собственный экземпляр объекта. - передать объект в threadstart в качестве состояния или сделать метод threadstart членом объекта, который будет "иметь" поток, и создать новый экземпляр для каждого потока, который вы создаете.
Изменить (в ответ на замечание Кацкула. Здесь пример инкапсуляции структуры
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.
}
Я закончил реализацию и тестирование версии того, что я изначально предложил:
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;
}
}
Хотя я все еще не уверен, когда ваш случай использования будет иметь смысл (см. мой комментарий к самому вопросу), я хотел бы привести рабочий пример, который, на мой взгляд, более читабельен, чем локальное хранилище (будь то статическое или экземпляр). В примере используется .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
На основе вашего прецедента я предполагаю, что это то, чего вы пытаетесь достичь. В примере сначала добавляются два обратных вызова из двух разных контекстов, основного и рабочего потоков. Затем пример запускает уведомление сначала из основного, а затем из рабочих потоков. Выполняемые обратные вызовы эффективно фильтруются текущим идентификатором потока. Просто чтобы показать, что все работает так, как ожидается, пример добавляет еще два обратных вызова (всего 4) и снова запускает уведомление из контекста основного и рабочего потоков.
Обратите внимание, что класс Notifier является обычным экземпляром, который может иметь состояние, несколько экземпляров и т.д. (опять же, по вашему варианту использования вашего вопроса). В примере не используется статический или поточно-статический или поточно-локальный.
Я был бы признателен, если бы вы могли посмотреть на код и сообщить мне, если я неправильно понял, чего вы пытаетесь достичь, или если такая техника будет соответствовать вашим потребностям.
Я не уверен, как вы создаете свои потоки в первую очередь, но есть способы дать каждому потоку собственное поточное локальное хранилище, не используя хакерские обходные пути, такие как код, который вы отправили в своем вопросе.
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(ключ) - запускается только в контексте потока GC, поэтому, скорее всего, только создаст больше мусора при создании другой таблицы поиска.
Вам нужно удалить объект из таблицы поиска каждого потока, который обратился к ThreadLocal. Единственный элегантный способ, который я могу решить, - это использование слабого ключевого словаря - структуры данных, которая странно отсутствует в С#.