with threads thread net multiple example español ejemplo book c# .net multithreading attributes

net - ¿C#tiene un análogo "ThreadLocal"(para miembros de datos) al atributo "ThreadStatic"?



thread c# ejemplo (8)

Aunque todavía no estoy seguro de cuándo su caso de uso tendría sentido (vea mi comentario sobre la pregunta en sí), me gustaría contribuir con un ejemplo funcional que, en mi opinión, es más legible que el almacenamiento local de subprocesos (ya sea estático o de instancia) . El ejemplo está usando .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(); } } } }

Cuando compile y ejecute el programa anterior, debería obtener una salida como esta: texto alternativo http://img695.imageshack.us/img695/991/threadlocal.png

Basado en su caso de uso, asumo que esto es lo que está tratando de lograr. El ejemplo primero agrega dos devoluciones de llamada de dos contextos diferentes, hilos principales y de trabajo. Luego, el ejemplo ejecuta la notificación primero desde los hilos principales y luego desde los subprocesos de trabajo. Las devoluciones de llamada que se ejecutan se filtran efectivamente por el ID de subproceso actual. Solo para mostrar que las cosas funcionan como se esperaba, el ejemplo agrega dos devoluciones de llamada más (para un total de 4) y nuevamente ejecuta la notificación desde el contexto de los hilos principales y de trabajo.

Tenga en cuenta que la clase Notifier es una instancia regular que puede tener estado, varias instancias, etc. (nuevamente, según el caso de uso de su pregunta). El ejemplo no utiliza static ni thread-static ni thread-local.

Le agradecería que leyera el código y me hiciera saber si entendí mal lo que está tratando de lograr o si una técnica como esta satisfaría sus necesidades.

He encontrado que el atributo "ThreadStatic" es extremadamente útil recientemente, pero ahora me hace querer un atributo de tipo "ThreadLocal" que me permite tener miembros de datos no estáticos por subproceso.

Ahora soy consciente de que esto tendría algunas implicaciones no triviales, pero:

¿Existe tal cosa ya incorporada en C # / .net? o como parece tan lejos que la respuesta a esta pregunta es no (para .net <4.0), ¿existe alguna implementación comúnmente utilizada?

Puedo pensar en una manera razonable de implementarlo, pero solo usaría algo que ya existía si estuviera disponible.

Ejemplo de Straw Man que implementaría lo que estoy buscando si no existe:

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

Por favor, perdona cualquier ignorancia de C #. Soy un desarrollador de C ++ que recientemente ha estado introduciendo las funciones más interesantes de C # y .net

Estoy limitado a .net 3.0 y quizás 3.5 (el proyecto pronto se moverá a 3.5).

El caso de uso específico son las listas de devolución de llamada que son específicas de subprocesos (usando el atributo [ThreadLocal] imaginario) 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(); } }


Considerar:

En lugar de intentar dar a cada variable miembro de un objeto un valor específico de subproceso, asigne a cada subproceso su propia instancia de objeto. - pase el objeto al inicio de subproceso como estado o convierta el método threadstart en un miembro del objeto que el subproceso "poseerá", y cree una nueva instancia para cada subproceso que genere.

Editar (en respuesta a la observación de Catskul. Aquí hay un ejemplo de cómo encapsular la estructura

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

Ahora aquí está la opción 2 (pasar la estructura como estado)

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


Deberías pensarlo dos veces. Esencialmente estás creando una pérdida de memoria. Cada objeto creado por el hilo permanece referenciado y no puede ser recolectado como basura. Hasta que el hilo termine.


No estoy seguro de cómo estás generando tus hilos en primer lugar, pero hay formas de darle a cada hilo su propio almacenamiento local de hilos, sin usar soluciones piratas como el código que publicaste en tu pregunta.

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 }

Obviamente, esto ignora la espera en los subprocesos para finalizar su trabajo, asegurándose de que los accesos a cualquier estado compartido sean seguros para subprocesos en todos los subprocesos de trabajo, pero se entiende la idea.


Si bien la solución publicada parece elegante, gotea objetos. El finalizador, LookupTable.Remove (key), se ejecuta solo en el contexto del subproceso del GC, por lo que es probable que solo esté creando más basura en la creación de otra tabla de búsqueda.

Debe eliminar el objeto de la tabla de búsqueda de cada subproceso que haya accedido a ThreadLocal. La única forma elegante en que puedo pensar en resolver esto es a través de un diccionario de clave débil, una estructura de datos que extrañamente falta en c #.



Terminé implementando y probando una versión de lo que había sugerido originalmente:

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