Est-ce que C # a un analogue «ThreadLocal» (pour les membres de données) avec l’atsortingbut «ThreadStatic»?

J’ai récemment trouvé l’ atsortingbut “ThreadStatic” extrêmement utile, mais je souhaite maintenant un atsortingbut de type “ThreadLocal” qui me permette d’avoir des données membres non statiques par thread.

Maintenant, je suis conscient que cela aurait des implications non sortingviales, mais:

Une telle chose existe-t-elle déjà dans C # / .net? ou puisqu’il semble jusqu’à présent que la réponse à cette question soit non (pour .net <4.0), existe-t-il une implémentation couramment utilisée?

Je peux penser à un moyen raisonnable de le mettre en œuvre moi-même, mais utiliserais simplement quelque chose qui existait déjà s’il était disponible.

Exemple de Straw Man qui implémenterait ce que je cherche s’il n’existe pas déjà:

class Foo { [ThreadStatic] static Dictionary threadLocalValues = new Dictionary(); int defaultValue = 0; int ThreadLocalMember { get { int value = defaultValue; if( ! threadLocalValues.TryGetValue(this, out value) ) { threadLocalValues[this] = value; } return value; } set { threadLocalValues[this] = value; } } } 

S’il vous plaît pardonnez toute ignorance C #. Je suis un développeur C ++ qui s’est récemment intéressé aux fonctionnalités les plus intéressantes de C # et de .net.

Je suis limité à .net 3.0 et peut-être 3.5 (le projet a / va bientôt passer à 3.5).

Le cas d’utilisation spécifique est une liste de rappel spécifique à un thread (utilisant l’atsortingbut imaginary [ThreadLocal]) à la fois:

 class NonSingletonSharedThing { [ThreadLocal] List callbacks; public void ThreadLocalRegisterCallback( Callback somecallback ) { callbacks.Add(somecallback); } public void ThreadLocalDoCallbacks(); { foreach( var callback in callbacks ) callback.invoke(); } } 

Entrez .NET 4.0!

Si vous êtes bloqué dans la version 3.5 (ou antérieure), vous devez examiner certaines fonctions , comme AllocateDataSlot qui devrait faire ce que vous voulez.

Vous devriez y penser à deux fois. Vous créez essentiellement une fuite de mémoire. Chaque object créé par le fil rest référencé et ne peut pas être récupéré. Jusqu’à ce que le fil se termine.

Si vous souhaitez stocker des données uniques par thread, vous pouvez utiliser Thread.SetData. Assurez-vous de lire sur les avantages et les inconvénients http://msdn.microsoft.com/en-us/library/6sby1byh.aspx car cela a des conséquences en termes de performances.

Considérer:

Plutôt que d’essayer de donner à chaque variable membre d’un object une valeur spécifique à un thread, atsortingbuez à chaque thread sa propre instance d’object. – transmettez l’object au threadstart en tant qu’état ou faites de la méthode threadstart un membre de l’object que le thread “possédera” et créez une nouvelle instance pour chaque thread que vous créez.

Edit (en réponse à la remarque de Catskul. Voici un exemple d’encapsulation de la structure

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();
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(); 

Maintenant, voici l’option 2 (passer le 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. }
{ // (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. } 

J’ai fini par implémenter et tester une version de ce que j’avais initialement suggéré:

 public class ThreadLocal { [ThreadStatic] private static Dictionary _lookupTable; private Dictionary LookupTable { get { if ( _lookupTable == null) _lookupTable = new Dictionary(); 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; } } 

Bien que je ne sois pas encore sûr de la pertinence de votre cas d’utilisation (voir mon commentaire sur la question elle-même), je souhaiterais proposer un exemple concret qui, à mon avis, est plus lisible que le stockage local à thread (qu’il soit statique ou par exemple). . L’exemple utilise .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 callback) { var id = Thread.CurrentThread.ManagedThreadId; lock (this._callbacks) { List> list; if (!this._callbacks.TryGetValue(id, out list)) { this._callbacks[id] = list = new List>(); } list.Add(callback); } } public void Execute() { var id = Thread.CurrentThread.ManagedThreadId; IEnumerable> threadCallbacks; ssortingng status; lock (this._callbacks) { status = ssortingng.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 SsortingngBuilder(); foreach (var callback in threadCallbacks) { b.AppendLine(callback()); } Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine(status); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(b.ToSsortingng()); } private readonly Dictionary>> _callbacks = new Dictionary>>(); } public static class Program { public static void Main(ssortingng[] 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(() => ssortingng.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(() => ssortingng.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(() => ssortingng.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(() => ssortingng.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(); } } } } 

Lorsque vous comstackz et exécutez le programme ci-dessus, vous devriez obtenir une sortie comme celle-ci: alt text http://img695.imageshack.us/img695/991/threadlocal.png

Sur la base de votre cas d’utilisation, je suppose que c’est ce que vous essayez d’atteindre. L’exemple ajoute en premier lieu deux rappels provenant de deux contextes différents, les threads principal et de travail. Ensuite, l’exemple lance la notification en premier à partir des threads principaux, puis des workwer. Les callbacks exécutés sont effectivement filtrés par l’ID de thread actuel. Juste pour montrer que les choses fonctionnent comme prévu, l’exemple ajoute deux rappels supplémentaires (pour un total de 4) et exécute à nouveau la notification à partir du contexte des threads principal et de travail.

Notez que la classe Notifier est une instance régulière pouvant avoir un état, plusieurs instances, etc. (encore une fois, selon le cas d’utilisation de votre question). Cet exemple n’utilise pas statique ou thread-static ou thread-local.

Je vous serais reconnaissant de bien vouloir consulter le code et de me laisser savoir si j’ai mal compris ce que vous essayez d’obtenir ou si une technique comme celle-ci répondrait à vos besoins.

Je ne suis pas sûr de savoir comment vous créez vos threads en premier lieu, mais il existe des moyens de donner à chaque thread son propre stockage de threads local, sans utiliser de solutions de rechange hackish comme le code que vous avez posté dans votre 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 } 

Cela ignore évidemment d'attendre que les threads terminent leur travail, en s'assurant que les access à tout état partagé sont sécurisés pour tous les threads de travail, mais vous voyez l'idée.

Bien que la solution affichée soit élégante, elle laisse fuir des objects. Le finaliseur – LookupTable.Remove (clé) – est exécuté uniquement dans le contexte du thread du GC. Il est donc probable qu’il ne créera plus de déchets lors de la création d’une autre table de recherche.

Vous devez supprimer un object de la table de recherche de chaque thread ayant accédé à ThreadLocal. La seule manière élégante de résoudre ce problème consiste à utiliser un dictionnaire à clé faible, une structure de données qui manque étrangement de c #.