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 { }
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? –
@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. –
NO use esta solución con la última versión de Unity, crea bucles infinitos. – Softlion