代码之家  ›  专栏  ›  技术社区  ›  Rory MacLeod

是否可以使Unity始终不抛出SynchronizationLockException?

  •  64
  • Rory MacLeod  · 技术社区  · 16 年前

    Unity依赖注入容器有一个众所周知的问题,SynchronizedLifetimeManager通常会导致Monitor.Exit方法抛出SynchronizationLockException,然后捕获并忽略该异常。这对我来说是个问题,因为我喜欢用visualstudio设置为在任何抛出的异常上中断进行调试,所以每次我的应用程序启动时,我都会无缘无故地多次中断这个异常。

    如何防止抛出此异常?

    在web上其他地方提到这个问题时,建议通常包括更改调试器设置以忽略它。这类似于去看医生,然后说,“医生,医生,当我举起它的时候,我的胳膊疼了,”被告知,“好吧,停止举起它。”我正在寻找一个解决方案,首先停止抛出异常。

    异常发生在SetValue方法中,因为它假定将首先调用GetValue,在这里调用Monitor.Enter。但是,lifetimestractegy和UnityDefaultBehaviorExtension类都定期调用SetValue而不调用GetValue。

    8 回复  |  直到 16 年前
        1
  •  38
  •   Drew Noakes    15 年前

    我确信有很多方法代码可以调用SynchronizedLifetimeManager,或者像ContainerControlledLifetimeManager这样的子代,但是有两种特别的场景给我带来了问题。

    第一个是我自己的错误-我使用构造函数注入来提供对容器的引用,在该构造函数中,我还将类的新实例添加到容器中以供将来使用。这种向后的方法的效果是将生存期管理器从Transient更改为ContainerControlled,从而使名为GetValue on的对象统一性与名为SetValue on的对象不同。吸取的教训是

    第二种情况是,每次调用RegisterInstance时,UnityDefaultBehaviorExtension都会调用SetValue,而不首先调用GetValue。幸运的是,团结是可以扩展的,只要有足够的血性思维,你就可以解决这个问题。

    从以下新的行为扩展开始:

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

    然后需要一种方法来替换默认行为。Unity没有删除特定扩展的方法,因此您必须删除所有内容,然后重新放入其他扩展:

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

    UnityClearBuildPlanStrategies ? RemoveAllExtensions清除了容器中所有策略和策略的内部列表(除了一个),因此我必须使用另一个扩展来避免在还原默认扩展时插入重复的扩展:

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

    现在,您可以安全地使用RegisterInstance,而不用担心被推向疯狂的边缘。当然,这里有一些测试:

    [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 { }
    
        2
  •  12
  •   Michael Kohne    11 年前

    Fixed 在Unity的最新版本(2.1.505.2)中。通过NuGet获取。

        3
  •  10
  •   Ade Miller    15 年前

    很遗憾,您的问题的答案是否定的。我在Microsoft patterns&实践小组(直到最近我还是那里的开发负责人),我们把它作为一个bug考虑到entlib5.0中。我们做了一些调查,得出的结论是,这是由我们的代码和调试器之间的一些意外交互造成的。我们确实考虑过修复,但结果证明这比现有代码更复杂。最后,这被排在了其他事情之后,并没有成为5的标准。

        4
  •  7
  •   Igor Berger Koen VV    14 年前

    我使用这个简短的解决方案:

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

    像这样使用:

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

    当做, 科恩

        5
  •  4
  •   Zubin Appoo    15 年前

    罗里的解决方案很好-谢谢。解决了每天困扰我的问题! 我对Rory的解决方案做了一些小的调整,这样它就可以处理注册的任何扩展(在我的例子中,我有一个wpfprism/复合扩展)。。

        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);
                }
            }
        }
    
        6
  •  1
  •   user556903 user556903    13 年前

    注意祖宾阿波的回答中的一个错误:有 UnityClearBuildPlanStrategies公司

    正确的代码段是:

    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);
       }
    }
    
        7
  •  0
  •   huoxudong125    12 年前
        8
  •  -8
  •   Alex G    16 年前

    这可能会帮助您:

    • 转到调试->例外。。。
    • 找出真正让你不安的异常,比如SynchronizationLockException

    推荐文章