c# .net-4.0 idisposable thread-local

c# - ¿Cuál es la forma correcta de desechar los elementos contenidos dentro de un ThreadLocal<IDisposable>?



.net-4.0 thread-local (6)

Cuando utiliza un ThreadLocal<T> y T implementa IDisposable, ¿cómo se supone que debe deshacerse de los miembros que se encuentran dentro de ThreadLocal?

Según ILSpy, los métodos Dispose () y Dispose (bool) de ThreadLocal son

public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { int currentInstanceIndex = this.m_currentInstanceIndex; if (currentInstanceIndex > -1 && Interlocked.CompareExchange(ref this.m_currentInstanceIndex, -1, currentInstanceIndex) == currentInstanceIndex) { ThreadLocal<T>.s_availableIndices.Push(currentInstanceIndex); } this.m_holder = null; }

No parece que ThreadLocal intente llamar a Dispose en sus miembros secundarios. No puedo decir cómo hacer referencia a cada hilo que internamente ha asignado para que pueda ocuparme de él.

Ejecuté una prueba con el siguiente código, la clase nunca fue eliminada

static class Sandbox { static void Main() { ThreadLocal<TestClass> test = new ThreadLocal<TestClass>(); test.Value = new TestClass(); test.Dispose(); Console.Read(); } } class TestClass : IDisposable { public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected void Dispose(bool Disposing) { Console.Write("I was disposed!"); } }


¿Cómo se llama el método ThreadLocal.Dispose? Yo esperaría que lo más probable es que esté dentro de algo así como un bloque "usar". Sugeriría que uno envuelva el bloque "usar" para el ThreadLocal con un bloque "usar" para el recurso que se va a almacenar allí.


En .NET 4.5, la propiedad Values se agregó a ThreadLocal <> para resolver el problema de administrar manualmente la vida útil de los objetos TheadLocal. Devuelve una lista de todas las instancias actuales vinculadas a esa variable ThreadLocal.

En este artículo de MSDN se presentó un ejemplo que usa un bucle Parallel.For para acceder a un grupo de conexiones de base de datos ThreadLocal. El fragmento de código relevante está debajo.

var threadDbConn = new ThreadLocal<MyDbConnection>(() => MyDbConnection.Open(), true); try { Parallel.For(0, 10000, i => { var inputData = threadDbConn.Value.GetData(i); ... }); } finally { foreach(var dbConn in threadDbConn.Values) { dbConn.Close(); } }


Esto está relacionado con ThreadLocal <> y la pérdida de memoria

Supongo que porque no hay IDisposable restricción IDisposable en T , se supone que el usuario de ThreadLocal<T> se deshará del objeto local, cuando corresponda.


La referencia de MSDN indica que los valores de ThreadLocal deben eliminarse mediante el hilo y usarlos una vez que estén listos. Sin embargo, en algunos casos, como el enhebrado de eventos utilizando un grupo de hilos, un hilo puede usar el valor e irse para hacer otra cosa y luego volver al valor N número de veces.

Un ejemplo específico es donde quiero que un DBContext de Entity Framework persista durante toda la vida útil de una serie de subprocesos de trabajador de bus de servicio.

He escrito la siguiente clase que utilizo en estas instancias: O bien DisposeThreadCompletedValues ​​puede ser llamado manualmente de vez en cuando por otro hilo o el subproceso del monitor interno puede ser activado

Espero que esto ayude?

using System.Threading; public class DisposableThreadLocal<T> : IDisposable where T : IDisposable { public DisposableThreadLocal(Func<T> _ValueFactory) { Initialize(_ValueFactory, false, 1); } public DisposableThreadLocal(Func<T> _ValueFactory, bool CreateLocalWatcherThread, int _CheckEverySeconds) { Initialize(_ValueFactory, CreateLocalWatcherThread, _CheckEverySeconds); } private void Initialize(Func<T> _ValueFactory, bool CreateLocalWatcherThread, int _CheckEverySeconds) { m_ValueFactory = _ValueFactory; m_CheckEverySeconds = _CheckEverySeconds * 1000; if (CreateLocalWatcherThread) { System.Threading.ThreadStart WatcherThreadStart; WatcherThreadStart = new ThreadStart(InternalMonitor); WatcherThread = new Thread(WatcherThreadStart); WatcherThread.Start(); } } private object SyncRoot = new object(); private Func<T> m_ValueFactory; public Func<T> ValueFactory { get { return m_ValueFactory; } } private Dictionary<Thread, T> m_InternalDict = new Dictionary<Thread, T>(); private Dictionary<Thread, T> InternalDict { get { return m_InternalDict; } } public T Value { get { T Result; lock(SyncRoot) { if (!InternalDict.TryGetValue(Thread.CurrentThread,out Result)) { Result = ValueFactory.Invoke(); InternalDict.Add(Thread.CurrentThread, Result); } } return Result; } set { lock (SyncRoot) { if (InternalDict.ContainsKey(Thread.CurrentThread)) { InternalDict[Thread.CurrentThread] = value; } else { InternalDict.Add(Thread.CurrentThread, value); } } } } public bool IsValueCreated { get { lock (SyncRoot) { return InternalDict.ContainsKey(Thread.CurrentThread); } } } public void DisposeThreadCompletedValues() { lock (SyncRoot) { List<Thread> CompletedThreads; CompletedThreads = new List<Thread>(); foreach (Thread ThreadInstance in InternalDict.Keys) { if (!ThreadInstance.IsAlive) { CompletedThreads.Add(ThreadInstance); } } foreach (Thread ThreadInstance in CompletedThreads) { InternalDict[ThreadInstance].Dispose(); InternalDict.Remove(ThreadInstance); } } } private int m_CheckEverySeconds; private int CheckEverySeconds { get { return m_CheckEverySeconds; } } private Thread WatcherThread; private void InternalMonitor() { while (!IsDisposed) { System.Threading.Thread.Sleep(CheckEverySeconds); DisposeThreadCompletedValues(); } } private bool IsDisposed = false; public void Dispose() { if (!IsDisposed) { IsDisposed = true; DoDispose(); } } private void DoDispose() { if (WatcherThread != null) { WatcherThread.Abort(); } //InternalDict.Values.ToList().ForEach(Value => Value.Dispose()); foreach (T Value in InternalDict.Values) { Value.Dispose(); } InternalDict.Clear(); m_InternalDict = null; m_ValueFactory = null; GC.SuppressFinalize(this); } }


Normalmente, cuando no descarta explícitamente una clase que contiene un recurso administrado, el recolector de basura eventualmente lo ejecutará y se deshará de él. Para que esto suceda, la clase debe tener un finalizador que disponga de su recurso. Su clase de muestra no tiene un finalizador.

Ahora, para deshacerse de una clase que se encuentra dentro de un ThreadLocal<T> donde T es IDisposable , también tiene que hacerlo usted mismo. ThreadLocal<T> es solo un contenedor, no intentará adivinar cuál es el comportamiento correcto para su referencia envuelta cuando está dispuesto. La clase podría, por ejemplo, sobrevivir a su almacenamiento local de hilo.


ThreadLocal<T> un vistazo al código en ThreadLocal<T> para ver qué está haciendo el Dispose actual y parece ser mucho vudú. Obviamente, deshacerse de cosas relacionadas con hilos.

Pero no elimina los valores si T es desechable.

Ahora, tengo una solución - una ThreadLocalDisposables<T> , pero antes de dar la definición completa, vale la pena pensar en lo que debería suceder si escribes este código:

var tl = new ThreadLocalDisposables<IExpensiveDisposableResource>(); tl.Value = myEdr1; tl.Value = myEdr2; tl.Dispose();

¿ myEdr1 myEdr2 tanto myEdr2 como myEdr2 ? ¿O simplemente myEdr2 ? ¿O debería eliminar myEdr2 cuando se asignó myEdr2 ?

No está claro para mí cuál debería ser la semántica.

Sin embargo, tengo claro que si escribo este código:

var tl = new ThreadLocalDisposables<IExpensiveDisposableResource>( () => new ExpensiveDisposableResource()); tl.Value.DoSomething(); tl.Dispose();

Entonces esperaría que el recurso creado por la fábrica para cada hilo se elimine.

Así que no voy a permitir la asignación directa del valor desechable para ThreadLocalDisposables y solo permitiré al constructor de fábrica.

Aquí está ThreadLocalDisposables :

public class ThreadLocalDisposables<T> : IDisposable where T : IDisposable { private ThreadLocal<T> _threadLocal = null; private ConcurrentBag<T> _values = new ConcurrentBag<T>(); public ThreadLocalDisposables(Func<T> valueFactory) { _threadLocal = new ThreadLocal<T>(() => { var value = valueFactory(); _values.Add(value); return value; }); } public void Dispose() { _threadLocal.Dispose(); Array.ForEach(_values.ToArray(), t => t.Dispose()); } public override string ToString() { return _threadLocal.ToString(); } public bool IsValueCreated { get { return _threadLocal.IsValueCreated; } } public T Value { get { return _threadLocal.Value; } } }

¿Esto ayuda?