2010-05-20 12 views
64

El contenedor de inyección de dependencias de Unity tiene lo que parece ser un problema ampliamente conocido en el que SynchronizedLifetimeManager a menudo hará que el método Monitor.Exit arroje una SynchronizationLockException que luego se detecta e ignora. Esto es un problema para mí porque me gusta depurar con el conjunto de Visual Studio para romper cualquier excepción lanzada, por lo que cada vez que se inicia mi aplicación estoy rompiendo esta excepción varias veces sin ninguna razón.¿Se puede hacer que Unity no arroje SynchronizationLockException todo el tiempo?

¿Cómo puedo evitar que se produzca esta excepción?

Dondequiera que se mencione este problema en otra parte de la web, el consejo generalmente implica cambiar la configuración del depurador para ignorarlo. Esto es similar a ir al médico y decir: "Doctor, doctor, me duele el brazo cuando lo levanto", para que le digan: "Bueno, deje de levantarlo". Estoy buscando una solución que detenga la excepción lanzada en primer lugar.

La excepción se produce en el método SetValue porque supone que GetValue se habrá llamado primero, donde se llama a Monitor.Enter. Sin embargo, las clases LifetimeStrategy y UnityDefaultBehaviorExtension regularmente llaman a SetValue sin llamar a GetValue.

Preferiría no tener que cambiar el código fuente y mantener mi propia versión de Unity, entonces estoy esperando una solución donde pueda agregar una combinación de extensiones, políticas o estrategias al contenedor que garantice eso, si el administrador de por vida es un SynchronizedLifetimeManager, GetValue siempre se llama antes que cualquier otra cosa.

Respuesta

38

Estoy seguro de que hay muchas maneras en que el código podría llamar a SynchronizedLifetimeManager, o un descendiente como ContainerControlledLifetimeManager, pero había dos escenarios en particular que me causaban problemas.

La primera fue mi culpa: estaba usando inyección de constructor para proporcionar una referencia al contenedor, y en ese constructor también estaba agregando la nueva instancia de la clase al contenedor para uso futuro. Este enfoque hacia atrás tuvo el efecto de cambiar el administrador de por vida de Transitorio a ContainerControlled para que el objeto Unity llamado GetValue no fuera el mismo objeto al que llamó SetValue. La lección aprendida es no haga nada durante la creación que podría cambiar el administrador de por vida de un objeto.

El segundo caso es que cada vez que se llama a RegisterInstance, UnityDefaultBehaviorExtension llama a SetValue sin llamar primero a GetValue. Afortunadamente, Unity es lo suficientemente extensible como para que, con suficiente mente sanguinaria, puedas evitar el problema.

de inicio con una nueva extensión comportamiento de la siguiente manera:

/// <summary> 
/// Replaces <see cref="UnityDefaultBehaviorExtension"/> to eliminate 
/// <see cref="SynchronizationLockException"/> exceptions that would otherwise occur 
/// when using <c>RegisterInstance</c>. 
/// </summary> 
public class UnitySafeBehaviorExtension : UnityDefaultBehaviorExtension 
{ 
    /// <summary> 
    /// Adds this extension's behavior to the container. 
    /// </summary> 
    protected override void Initialize() 
    { 
     Context.RegisteringInstance += PreRegisteringInstance; 

     base.Initialize(); 
    } 

    /// <summary> 
    /// Handles the <see cref="ExtensionContext.RegisteringInstance"/> event by 
    /// ensuring that, if the lifetime manager is a 
    /// <see cref="SynchronizedLifetimeManager"/> that its 
    /// <see cref="SynchronizedLifetimeManager.GetValue"/> method has been called. 
    /// </summary> 
    /// <param name="sender">The object responsible for raising the event.</param> 
    /// <param name="e">A <see cref="RegisterInstanceEventArgs"/> containing the 
    /// event's data.</param> 
    private void PreRegisteringInstance(object sender, RegisterInstanceEventArgs e) 
    { 
     if (e.LifetimeManager is SynchronizedLifetimeManager) 
     { 
      e.LifetimeManager.GetValue(); 
     } 
    } 
} 

entonces usted necesita una manera de reemplazar el comportamiento predeterminado. La unidad no tener un método para eliminar una extensión específica, por lo que tenga que quitar todo y poner las otras extensiones hacia atrás de nuevo:

public static IUnityContainer InstallCoreExtensions(this IUnityContainer container) 
{ 
    container.RemoveAllExtensions(); 
    container.AddExtension(new UnityClearBuildPlanStrategies()); 
    container.AddExtension(new UnitySafeBehaviorExtension()); 

#pragma warning disable 612,618 // Marked as obsolete, but Unity still uses it internally. 
    container.AddExtension(new InjectedMembers()); 
#pragma warning restore 612,618 

    container.AddExtension(new UnityDefaultStrategiesExtension()); 

    return container; 
} 

en cuenta que UnityClearBuildPlanStrategies? RemoveAllExtensions borra todas las listas internas del contenedor de políticas y estrategias a excepción de uno, así que tuve que usar otra extensión para evitar la inserción de duplicados cuando restauró el extensiones predeterminadas:

/// <summary> 
/// Implements a <see cref="UnityContainerExtension"/> that clears the list of 
/// build plan strategies held by the container. 
/// </summary> 
public class UnityClearBuildPlanStrategies : UnityContainerExtension 
{ 
    protected override void Initialize() 
    { 
     Context.BuildPlanStrategies.Clear(); 
    } 
} 

Ahora se puede utilizar con seguridad registerInstance sin miedo de ser llevado al borde de la locura. Solo para estar seguro, he aquí algunas pruebas:

[TestClass] 
public class UnitySafeBehaviorExtensionTests : ITest 
{ 
    private IUnityContainer Container; 
    private List<Exception> FirstChanceExceptions; 

    [TestInitialize] 
    public void TestInitialize() 
    { 
     Container = new UnityContainer(); 
     FirstChanceExceptions = new List<Exception>(); 
     AppDomain.CurrentDomain.FirstChanceException += FirstChanceExceptionRaised; 
    } 

    [TestCleanup] 
    public void TestCleanup() 
    { 
     AppDomain.CurrentDomain.FirstChanceException -= FirstChanceExceptionRaised; 
    } 

    private void FirstChanceExceptionRaised(object sender, FirstChanceExceptionEventArgs e) 
    { 
     FirstChanceExceptions.Add(e.Exception); 
    } 

    /// <summary> 
    /// Tests that the default behavior of <c>UnityContainer</c> leads to a <c>SynchronizationLockException</c> 
    /// being throw on <c>RegisterInstance</c>. 
    /// </summary> 
    [TestMethod] 
    public void UnityDefaultBehaviorRaisesExceptionOnRegisterInstance() 
    { 
     Container.RegisterInstance<ITest>(this); 

     Assert.AreEqual(1, FirstChanceExceptions.Count); 
     Assert.IsInstanceOfType(FirstChanceExceptions[0], typeof(SynchronizationLockException)); 
    } 

    /// <summary> 
    /// Tests that <c>UnitySafeBehaviorExtension</c> protects against <c>SynchronizationLockException</c>s being 
    /// thrown during calls to <c>RegisterInstance</c>. 
    /// </summary> 
    [TestMethod] 
    public void SafeBehaviorPreventsExceptionOnRegisterInstance() 
    { 
     Container.RemoveAllExtensions(); 
     Container.AddExtension(new UnitySafeBehaviorExtension()); 
     Container.AddExtension(new InjectedMembers()); 
     Container.AddExtension(new UnityDefaultStrategiesExtension()); 

     Container.RegisterInstance<ITest>(this); 

     Assert.AreEqual(0, FirstChanceExceptions.Count); 
    } 
} 

public interface ITest { } 
+0

Estamos utilizando Enterprise Library Logger pero no Unity y tenemos el mismo problema ya que el registrador usa Unity internamente. ¿Puedo aplicar esa solución sin Unity? –

+0

@DmitryGusarov No lo he intentado, pero si puede obtener una referencia a la instancia 'UnityContainer' que EntLib usa internamente, entonces debería poder usar el método' InstallCoreExtensions' en ella. Debería comprobar que EntLib no agrega ninguna extensión propia; si lo hace, deberá volver a agregarlos después de que 'InstallCoreExtensions' elimine todo. –

+4

NO use esta solución con la última versión de Unity, crea bucles infinitos. – Softlion

-8

Esto podría ayudarle a:

  • Ir a Depurar -> Excepciones ...
  • Encuentra las excepciones que realmente molesta te gusta SynchronizationLockException

Voila.

+3

Eso es lo que quise decir con "el consejo generalmente implica cambiar la configuración del depurador para ignorarlo". No quiero aplastar ninguna SynchronizationLockExceptions que los errores en mi propio código puedan estar causando. –

10

Desafortunadamente, la respuesta a su pregunta es no. Seguí esto con el equipo de desarrollo aquí en el grupo de prácticas de Microsoft patterns & (yo era el líder de desarrollo hasta hace poco) y teníamos esto como un error a considerar para EntLib 5.0. Hicimos una investigación y llegamos a la conclusión de que esto fue causado por algunas interacciones inesperadas entre nuestro código y el depurador. Consideramos una solución, pero resultó ser más compleja que el código existente. Al final esto obtuvo prioridad debajo de otras cosas y no hizo la barra para 5.

Lo siento, no tengo una mejor respuesta para usted. Si te sirve de consuelo, también me resulta irritante.

+1

¡Gracias por la respuesta de la boca del caballo! Afortunadamente, Unity es lo suficientemente extensible como para poder solucionar el problema; vea mi [respuesta] (http://stackoverflow.com/questions/2873767/can-unity-be-made-to-not-throw-synchronizationlockexception -all-the-time/3153585 # 3153585). –

+0

Ade resumió perfectamente la decisión que el equipo de EntLib pasó en el momento del desarrollo de EntLib5.0. Si esto sigue siendo un factor irritante para muchos, sugiérelo en nuestro sitio de usuario y vote por él. Lo consideraremos al planear vNext de Unity y EntLib. –

4

La solución de Rory es genial, gracias. ¡Resolvió un problema que me molesta todos los días! he hecho algunos ajustes menores a la solución de Rory para que maneje lo extensiones están registrados (en mi caso yo tenía una extensión de WPF Prisma/Compuesto) ..

public static void ReplaceBehaviourExtensionsWithSafeExtension(IUnityContainer container) 
    { 
     var extensionsField = container.GetType().GetField("extensions", BindingFlags.Instance | BindingFlags.NonPublic); 
     var extensionsList = (List<UnityContainerExtension>)extensionsField.GetValue(container); 
     var existingExtensions = extensionsList.ToArray(); 
     container.RemoveAllExtensions(); 
     container.AddExtension(new UnitySafeBehaviorExtension()); 
     foreach (var extension in existingExtensions) 
     { 
      if (!(extension is UnityDefaultBehaviorExtension)) 
      { 
       container.AddExtension(extension); 
      } 
     } 
    } 
+0

Recuerde borrar las estrategias del plan de compilación - 'container.AddExtension (new UnityClearBuildPlanStrategies());' –

1

Beware a un error en la respuesta de Zubin Appoo: existe UnityClearBuildPlanStrategies falta en su código.

El fragmento de código correcto es:

FieldInfo extensionsField = container.GetType().GetField("extensions", BindingFlags.Instance | BindingFlags.NonPublic); 
List<UnityContainerExtension> extensionsList = (List<UnityContainerExtension>)extensionsField.GetValue(container); 
UnityContainerExtension[] existingExtensions = extensionsList.ToArray(); 
container.RemoveAllExtensions(); 
container.AddExtension(new UnityClearBuildPlanStrategiesExtension()); 
container.AddExtension(new UnitySafeBehaviorExtension()); 

foreach (UnityContainerExtension extension in existingExtensions) 
{ 
    if (!(extension is UnityDefaultBehaviorExtension)) 
    { 
     container.AddExtension(extension); 
    } 
} 
+1

Este fragmento de código es idéntico a la respuesta de Zubin y no incluye la llamada a UnityClearBuildPlanStrategies() –

+0

Tiene razón, fijo –

7

utilizo este breve solución:

/// <summary> 
/// KVV 20110502 
/// Fix for bug in Unity throwing a synchronizedlockexception at each register 
/// </summary> 
class LifeTimeManager : ContainerControlledLifetimeManager 
{ 
    protected override void SynchronizedSetValue(object newValue) 
    { 
     base.SynchronizedGetValue(); 
     base.SynchronizedSetValue(newValue); 
    } 
} 

y utilizar de esta manera:

private UnityContainer _container; 
... 
_container.RegisterInstance(instance, new LifeTimeManager()); 

el problema es que la clase base de ContainerControlledLifetimeManager espera que SynchronizedSetValue haga un monitor. Ingrese() a través de la base.GetValue, ho sin embargo, la clase ContainerControlledLifetimeManager no puede hacer esto (al parecer, sus desarrolladores no tienen habilitado 'break at exception'?).

cordiales, Koen

+0

Este es un ¡buena solución para Silverlight! – Kit

+0

¿Cómo puedo introducir esta corrección en la aplicación? No lo entiendo ... ¿Debería registrar LifeTimeManager en alguna parte? No estamos usando la Unidad ... Solo este Registrador ... –

12

Fixed en la última versión de la Unidad (2.1.505.2). Obténgalo vía NuGet.

0

Unity 2.1 - August 2012 update fix the bug

  1. Abordar un problema de seguridad hilo: http://unity.codeplex.com/discussions/328841

  2. mejorar la experiencia de depuración en System.Threading.SynchronizationLockException: https://entlib.uservoice.com/forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep

  3. mejorar la experiencia de la depuración mediante una mejor mensajería error cuando un tipo no puede ser cargado: http://unity.codeplex.com/workitem/9223

  4. Apoyando un escenario de llevar a cabo una acumulación() en una instancia existente de una clase que no hace tener un constructor público: http://unity.codeplex.com/workitem/9460

para hacer la experiencia de actualización lo más simple posible para los usuarios y para evitar la nee d para las redirecciones de enlace de ensamblaje, optamos por incrementar solo la versión del archivo de ensamblaje, no la versión del ensamblado de .NET.

Cuestiones relacionadas