Can Unity be made to not throw SynchronizationLockException all the time?

前端 未结 8 1359
借酒劲吻你
借酒劲吻你 2020-11-29 18:36

The Unity dependency injection container has what seems to be a widely known issue where the SynchronizedLifetimeManager will often cause the Monitor.Exit method to throw a

相关标签:
8条回答
  • 2020-11-29 18:42

    Rory's solution is great - thanks. Solved an issue that annoys me every day! I made some minor tweaks to Rory's solution so that it handles whatever extensions are registered (in my case i had a WPF Prism/Composite extension)..

        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 讨论(0)
  • 2020-11-29 18:45

    The answer to your question is unfortunately no. I followed up on this with the dev team here at the Microsoft patterns & practices group (I was the dev lead there until recently)and we had this as a bug to consider for EntLib 5.0. We did some investigation and came to the conclusion that this was caused by some unexpected interactions between our code and the debugger. We did consider a fix but this turned out to be more complex than the existing code. In the end this got prioritized below other things and didn't make the bar for 5.

    Sorry I don't have a better answer for you. If it's any consolation I find it irritating too.

    0 讨论(0)
  • 2020-11-29 18:47

    Beware to one mistake in Zubin Appoo's answer: there is UnityClearBuildPlanStrategies missing in his code.

    The right code snippet is:

    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);
       }
    }
    
    0 讨论(0)
  • 2020-11-29 18:50

    This might help you:

    • Go to Debug -> Exceptions...
    • Find the exceptions that really upset you like SynchronizationLockException

    Voila.

    0 讨论(0)
  • 2020-11-29 18:54

    I'm sure there's a lot of ways code could call SynchronizedLifetimeManager, or a descendant like ContainerControlledLifetimeManager, but there were two scenarios in particular that were causing me problems.

    The first was my own fault - I was using constructor injection to supply a reference to the container, and in that constructor I was also adding the new instance of the class to the container for future use. This backwards approach had the effect of changing the lifetime manager from Transient to ContainerControlled so that the object Unity called GetValue on was not the same object it called SetValue on. The lesson learned is don't do anything during build-up that could change an object's lifetime manager.

    The second scenario was that every time RegisterInstance is called, UnityDefaultBehaviorExtension calls SetValue without calling GetValue first. Luckily, Unity is extensible enough that, with enough bloody-mindedness, you can work around the problem.

    Start with a new behavior extension like this:

    /// <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();
            }
        }
    }
    

    Then you need a way to replace the default behavior. Unity doesn't have a method to remove a specific extension, so you have to remove everything and put the other extensions back in again:

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

    Notice that UnityClearBuildPlanStrategies? RemoveAllExtensions clears out all of the container's internal lists of policies and strategies except for one, so I had to use another extension to avoid inserting duplicates when I restored the default extensions:

    /// <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();
        }
    }
    

    Now you can safely use RegisterInstance without fear of being driven to the brink of madness. Just to be sure, here's some tests:

    [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 讨论(0)
  • 2020-11-29 18:54

    Fixed in the latest release of Unity (2.1.505.2). Get it via NuGet.

    0 讨论(0)
提交回复
热议问题