c# .net inversion-of-control unity-container idisposable

c# - Unity 2.0 y manejo de tipos IDisponibles(especialmente con PerThreadLifetimeManager)



.net inversion-of-control (4)

Sé que una pregunta similar se hizo varias veces (por ejemplo: here , here , here y here ) pero fue en las versiones anteriores de Unity donde la respuesta dependía de LifetimeManager clase LifetimeManager usada.

La documentación dice:

Unity usa tipos específicos que heredan de la clase base LifetimeManager (denominados colectivamente como administradores de por vida) para controlar cómo almacena las referencias a las instancias de objetos y cómo el contenedor dispone de estas instancias.

Ok, suena bien, así que decidí verificar la implementación de los administradores de por vida. Mi conclusión:

  • TransientLifetimeManager - no hay manejo de la eliminación. El contenedor solo resuelve la instancia y no la rastrea. El código de llamada es responsable de eliminar la instancia.
  • ContainerControlledLifetimeManager : desecha la instancia cuando se elimina el administrador de por vida (= cuando se desecha el contenedor). Proporciona instancia singleton compartida entre todos los contenedores en la búsqueda.
  • HierarchicalLifetimeManager : deriva el comportamiento de ContainerControlledLifetimeManager . Proporciona instancia "singleton" por contenedor en alta búsqueda (subcontenedores).
  • ExternallyControlledLifetimeManager - no hay manejo de la eliminación. Comportamiento correcto porque el contenedor no es propietario de la instancia.
  • PerResolveLifetimeManager - no hay manejo de la eliminación. En general, es igual que TransientLifetimeManager pero permite reutilizar la instancia para la inyección de dependencia al resolver el gráfico de objetos completo.
  • PerThreadLifetimeManager : no hay manejo de la eliminación como también se describe en MSDN. ¿Quién es responsable de disponer?

La implementación del PerThreadLifetimeManager es:

public class PerThreadLifetimeManager : LifetimeManager { private readonly Guid key = Guid.NewGuid(); [ThreadStatic] private static Dictionary<Guid, object> values; private static void EnsureValues() { if (values == null) { values = new Dictionary<Guid, object>(); } } public override object GetValue() { object result; EnsureValues(); values.TryGetValue(this.key, out result); return result; } public override void RemoveValue() { } public override void SetValue(object newValue) { EnsureValues(); values[this.key] = newValue; } }

Por lo tanto, el contenedor de eliminación no dispone de las instancias desechables creadas con este administrador de por vida. La finalización del hilo tampoco eliminará esas instancias. Entonces, ¿quién es responsable de liberar instancias?

Intenté eliminar manualmente la instancia resuelta en el código y encontré otro problema. No puedo derribar el instinto. RemoveValue del administrador de por vida está vacío: una vez que se crea la instancia, no es posible eliminarlo del diccionario estático de hilos (también sospecho que el método TearDown no hace nada). Entonces, si llama a Resolve después de eliminar la instancia, obtendrá la instancia eliminada. Creo que esto puede ser un gran problema cuando se usa este administrador de por vida con subprocesos del grupo de subprocesos.

¿Cómo usar correctamente este administrador de por vida?

Además, esta implementación a menudo se reutiliza en administradores de por vida personalizados como PerCallContext, PerHttpRequest, PerAspNetSession, PerWcfCall, etc. Solo el diccionario estático de hilos se reemplaza por algún otro constructo.

¿También entiendo correctamente que el manejo de objetos desechables depende del administrador de por vida? Por lo tanto, el código de la aplicación depende del administrador de por vida utilizado.

Leí que en otros contenedores de IoC que manejan objetos desechables temporales los manejan los subcontenedores, pero no encontré un ejemplo para Unity; probablemente podría manejarse con el subcontenedor de ámbito local y HiearchicalLifetimeManager pero no estoy seguro de cómo hacerlo.


¿Sería una solución viable utilizar el evento HttpContext.Current.ApplicationInstance.EndRequest para enganchar al final de la solicitud y luego deshacerse del objeto almacenado en este administrador de por vida? al igual que:

public HttpContextLifetimeManager() { HttpContext.Current.ApplicationInstance.EndRequest += (sender, e) => { Dispose(); }; } public override void RemoveValue() { var value = GetValue(); IDisposable disposableValue = value as IDisposable; if (disposableValue != null) { disposableValue.Dispose(); } HttpContext.Current.Items.Remove(ItemName); } public void Dispose() { RemoveValue(); }

no tiene que usar un contenedor secundario como la otra solución y el código utilizado para desechar los objetos todavía está en el administrador de por vida como debería.


Al observar el código fuente de Unity 2.0, huele a que los LifetimeManagers se utilizan para mantener los objetos dentro del alcance de diferentes maneras para que el recolector de basura no se deshaga de ellos. Por ejemplo, con PerThreadLifetimeManager, usará ThreadStatic para mantener una referencia en cada objeto con la vida útil de ese hilo en particular. Sin embargo, no llamará a Desechar hasta que el contenedor esté Desechado.

Hay un objeto LifetimeContainer que se usa para mantener todas las instancias que se crean, luego se desecha cuando se desecha el UnityContainer (que, a su vez, desecha todos los IDisposables allí en orden cronológico inverso).

EDITAR: después de una inspección más cercana, LifetimeContainer solo contiene LifetimeManagers (de ahí el nombre de "Lifetime" Container). Así que cuando está dispuesta, solo dispone los administradores de por vida. (Y nos enfrentamos al problema que ya se discute).


Me encontré con este problema recientemente cuando estaba implementando Unity en mi aplicación. Las soluciones que encontré aquí en y en otros lugares en línea no parecían abordar el problema de manera satisfactoria, en mi opinión.

Cuando no se usa Unity, las instancias identificables tienen un patrón de uso bien entendido:

  1. Dentro de un alcance más pequeño que una función, póngalos en un bloque de using para obtener la eliminación "gratis".

  2. Cuando se crea para un miembro de instancia de una clase, implemente IDisposable en la clase y ponga limpieza en Dispose() .

  3. Cuando se pasa al constructor de una clase, no haga nada, ya que la instancia IDisposable es propiedad en otro lugar.

La unidad confunde las cosas porque cuando la inyección de dependencia se realiza correctamente, el caso # 2 anterior desaparece. Todas las dependencias deben ser inyectadas, lo que significa que esencialmente ninguna clase será propietaria de las instancias IDisposibles que se están creando. Sin embargo, tampoco proporciona una forma de "llegar a" los IDisposables que se crearon durante una llamada Resolve() , por lo que parece que no se puede usar el using bloques. ¿Qué opción queda?

Mi conclusión es que la interfaz Resolve() es esencialmente incorrecta. Devolver solo el tipo solicitado y los objetos con fugas que requieren un manejo especial como IDisposable no pueden ser correctos.

En respuesta, escribí la extensión IDisposableTrackingExtension para Unity, que rastrea las instancias IDisposable creadas durante una resolución de tipo y devuelve un objeto envoltorio desechable que contiene una instancia del tipo solicitado y todas las dependencias IDisposibles del gráfico de objetos.

Con esta extensión, la resolución de tipos se ve así (se muestra aquí usando una fábrica, ya que sus clases de negocios nunca deben tomar IUnityContainer como una dependencia):

public class SomeTypeFactory { // ... take IUnityContainer as a dependency and save it IDependencyDisposer< SomeType > Create() { return this.unity.ResolveForDisposal< SomeType >(); } } public class BusinessClass { // ... take SomeTypeFactory as a dependency and save it public void AfunctionThatCreatesSomeTypeDynamically() { using ( var wrapper = this.someTypeFactory.Create() ) { SomeType subject = wrapper.Subject; // ... do stuff } } }

Esto reconcilia los patrones de uso IDisposable # 1 y # 3 desde arriba. Las clases normales usan la inyección de dependencia; no poseen IDisposables inyectados, por lo que no se deshacen de ellos. Clases que realizan la resolución de tipos (a través de fábricas) porque necesitan objetos creados dinámicamente, esas clases son las propietarias y esta extensión proporciona la facilidad para administrar los ámbitos de disposición.