Sono sicuro che ci sono molti modi in cui il codice potrebbe chiamare SynchronizedLifetimeManager, o un discendente come ContainerControlledLifetimeManager, ma in particolare c'erano due scenari che mi causavano problemi.
Il primo è stato il mio errore - stavo usando il constructor injection per fornire un riferimento al contenitore, e in quel costruttore stavo anche aggiungendo la nuova istanza della classe al contenitore per un uso futuro. Questo approccio a ritroso ha avuto l'effetto di cambiare il gestore della vita da Transient a ContainerControlled in modo che l'oggetto Unity chiamato GetValue non fosse lo stesso oggetto su cui è stato impostato SetValue. La lezione appresa è non fare nulla durante il build che potrebbe cambiare il lifetime manager di un oggetto.
Il secondo scenario era che ogni volta che RegisterInstance viene chiamato, UnityDefaultBehaviorExtension chiama SetValue senza chiamare GetValue per primo. Fortunatamente, Unity è abbastanza estensibile da poter risolvere il problema con sufficiente malumore.
Inizia con una nuova estensione comportamento come questo:
/// <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();
}
}
}
allora avete bisogno di un modo per sostituire il comportamento predefinito. L'unità non dispone di un metodo per rimuovere un'estensione specifica, in modo da avere per rimuovere tutto e mettere gli altri interni nel nuovo:
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;
}
noti che UnityClearBuildPlanStrategies
? RemoveAllExtensions cancella fuori tutti liste interne del contenitore di politiche e strategie ad eccezione di uno, così ho dovuto usare un altro interno al fine di evitare l'inserimento di duplicati quando ho ripristinato le estensioni predefinite:
/// <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();
}
}
Ora si può tranquillamente utilizzare registerInstance senza paura di essere guidato sull'orlo della pazzia. Giusto per essere sicuri, ecco alcuni test: soluzione
[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 { }
Stiamo utilizzando Enterprise Library Logger ma non Unity e abbiamo lo stesso problema dal momento che il logger utilizza internamente Unity. Posso applicare quella correzione senza Unity? –
@DmitryGusarov Non l'ho provato, ma se riesci a ottenere un riferimento all'istanza 'UnityContainer' che EntLib usa internamente, dovresti essere in grado di usare il metodo' InstallCoreExtensions' su di esso. Dovresti controllare che EntLib non aggiunga alcuna sua estensione - se lo fa, dovrai aggiungerli di nuovo dopo "InstallCoreExtensions" rimuove tutto. –
NON usare questa soluzione con l'ultima versione di Unity, crea loop infiniti. – Softlion