¿Se puede hacer que Unity no lance SynchronizationLockException todo el tiempo?


El contenedor de inyección de dependencias de Unity tiene lo que parece ser un problema ampliamente conocido donde el SynchronizedLifetimeManager a menudo causará el Monitor.Método Exit para lanzar una SynchronizationLockException que luego se captura e ignora. Esto es un problema para mí porque me gusta depurar con Visual Studio configurado para romper en cualquier excepción lanzada, por lo que cada vez que se inicia mi aplicación estoy rompiendo en esta excepción varias veces sin ninguna razón.

¿Cómo puedo evitar esto excepción de ser lanzado?

Siempre que este problema se mencione 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 me digan: "Bueno, deja de levantarlo."Estoy buscando una solución que detenga la excepción en primer lugar.

La excepción ocurre en el método setValue porque asume que getValue han sido llamados primero, donde Monitor.Se llama Enter. Sin embargo, las clases LifetimeStrategy y UnityDefaultBehaviorExtension llaman regularmente a setValue sin llamar a getValue.

Prefiero no tener que cambiar el código fuente y mantener mi propia versión de Unity, por lo que espero una solución en la que pueda agregar alguna combinación de extensiones, políticas o estrategias al contenedor que garantice que, si lifetime manager es un SynchronizedLifetimeManager, getValue siempre esté llamé antes que nada.

Author: Rory MacLeod, 2010-05-20

8 answers

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

La primera fue culpa mía: estaba usando inyección de constructor para suministrar 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 la vida útil de Transient a containercontroled para que el objeto Unity llamado getValue on no fuera el mismo objeto al que se llamaba setValue on. La lección aprendida es no hacer nada durante la acumulación que pueda cambiar el gestor de vida útil de un objeto.

El segundo escenario era 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 sangrienta, puedas trabajar alrededor del problema.

Comience con una nueva extensión de comportamiento como esta:

/// <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 necesita una forma de reemplazar el comportamiento predeterminado. Unity no tiene un método para eliminar una extensión específica, por lo que debes eliminar todo y volver a colocar las otras extensiones:

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

Observe que UnityClearBuildPlanStrategies? RemoveAllExtensions borra todas las listas internas de políticas y estrategias del contenedor excepto una, por lo que tuve que usar otra extensión para evitar insertar duplicados cuando restauré las 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 puedes usar RegisterInstance de forma segura sin temor a ser llevado al borde de la locura. Solo para estar seguro, aquí hay 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 { }
 38
Author: Rory MacLeod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-19 17:58:09

Corregido en la última versión de Unity (2.1.505.2). Consíguelo a través de NuGet.

 12
Author: Grigori Melnik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-01 21:11:07

La respuesta a tu pregunta es desafortunadamente no. Seguí esto con el equipo de desarrollo aquí en el grupo de patrones y prácticas de Microsoft (yo era el líder de desarrollo allí hasta hace poco)y tuvimos esto como un error a considerar para EntLib 5.0. Hicimos algunas investigaciones 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 esto resultó ser más complejo que el código existente. Al final esto se priorizó debajo de otras cosas y no hizo la barra para 5.

Lo siento, no tengo una mejor respuesta para ti. Si te sirve de consuelo, yo también lo encuentro irritante.

 10
Author: Ade Miller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-25 19:56:31

Utilizo esta solución corta:

/// <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 úsalo así:

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

El problema es que la clase base de ContainerControlledLifetimeManager espera que un SynchronizedSetValue haga un monitor.Enter () a través de la base.getValue, sin embargo la clase ContainerControlledLifetimeManager falla al hacer esto (aparentemente sus desarrolladores no tenían 'break at exception' habilitado?).

Saludos, Koen

 7
Author: Koen VV,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-08 22:15:41

La solución de Rory es genial - gracias. Resuelto un problema que me molesta todos los días! Hice algunos ajustes menores a la solución de Rory para que maneje cualquier extensión registrada (en mi caso tenía una extensión WPF Prism/Composite)..

    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);
            }
        }
    }
 4
Author: Zubin Appoo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-21 03:38:42

Tenga cuidado con un error en la respuesta de Zubin Appoo: hay UnityClearBuildPlanStrategies que faltan 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
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-25 07:27:26

Unity 2.1-Actualización de agosto de 2012 corrige el error

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

  2. Mejora de la experiencia de depuración en el sistema.Enhebrando.Sincronizaciónlockexception: https://entlib.uservoice.com/forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep

  3. Mejorar la experiencia de depuración a través de una mejor mensajería de errores cuando un el tipo no se puede cargar: http://unity.codeplex.com/workitem/9223

  4. Soportar un escenario de realizar una BuildUp() en una instancia existente de una clase que no tiene un constructor público: http://unity.codeplex.com/workitem/9460

Para que la experiencia de actualización sea lo más simple posible para los usuarios y para evitar la necesidad de redirecciones de enlace de ensamblado, optamos por incrementar solo la versión del archivo de ensamblado, no el ensamblado. NET versión.

 0
Author: huoxudong125,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-10 09:14:53

Esto podría ayudarte:

  • Vaya a Debug -> Exceptions...
  • Encuentra las excepciones que realmente te molestan como SynchronizationLockException

Voila.

 -8
Author: Alex G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-15 18:54:09