可以使Unity不会一直抛出SynchronizationLockException吗?

Ror*_*eod 64 c# enterprise-library unity-container

Unity依赖注入容器似乎是一个众所周知的问题,其中SynchronizedLifetimeManager经常会导致Monitor.Exit方法抛出SynchronizationLockException,然后捕获并忽略它.这对我来说是一个问题,因为我喜欢使用Visual Studio进行调试以打破任何抛出的异常,所以每次我的应用程序启动时,我都会无缘无故地多次打破这个异常.

如何防止抛出此异常?

无论在Web上的其他地方提到此问题,建议通常都涉及更改调试器设置以忽略它.这类似于去看医生并且说:"医生,医生,当我举起它时,我的手臂疼,"被告知,"好吧,停止提高它." 我正在寻找一种解决方案,可以阻止异常被抛出.

SetValue方法中发生异常,因为它假设首先调用GetValue,并调用Monitor.Enter.但是,LifetimeStrategy和UnityDefaultBehaviorExtension类都会定期调用SetValue而不调用GetValue.

我宁愿不必更改源代码并维护我自己的Unity版本,所以我希望有一个解决方案,我可以在容器中添加一些扩展,策略或策略的组合,以确保,如果生命周期管理器是一个SynchronizedLifetimeManager,GetValue始终先调用.

Ror*_*eod 38

我确信代码可以通过很多方式调用SynchronizedLifetimeManager,或者像ContainerControlledLifetimeManager这样的后代,但有两种情况特别导致我出现问题.

第一个是我自己的错 - 我正在使用构造函数注入来提供对容器的引用,在该构造函数中,我还将该类的新实例添加到容器中以供将来使用.这种向后方法具有将生命周期管理器从Transient更改为ContainerControlled的效果,因此称为GetValue on的对象Unity与其调用SetValue的对象不同.所学到的教训是在构建期间不做任何可能改变对象的终身经理的事情.

第二种情况是每次调用RegisterInstance时,UnityDefaultBehaviorExtension都会调用SetValue而不先调用GetValue.幸运的是,Unity具有足够的可扩展性,只要有足够的血腥心态,就可以解决问题.

从这样的新行为扩展开始:

/// <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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您需要一种方法来替换默认行为.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;
}
Run Code Online (Sandbox Code Playgroud)

请注意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();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你可以安全地使用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 { }
Run Code Online (Sandbox Code Playgroud)

  • 不要将此解决方案与最新版本的Unity一起使用,它会创建无限循环. (4认同)

Gri*_*nik 12

在最新版本的Unity(2.1.505.2)中修复.通过NuGet获取它.


Ade*_*ler 10

不幸的是,你的问题的答案是否定的.我跟着微软模式和实践小组的开发团队跟进了这一点(直到最近我一直是那里的开发人员),我们把它作为EntLib 5.0的一个bug来考虑.我们进行了一些调查并得出结论,这是由我们的代码和调试器之间的一些意外交互引起的.我们确实考虑过修复,但事实证明这比现有代码更复杂.最后,这优先于其他东西,并没有成为5的标准.

对不起,我没有更好的答案.如果有任何安慰我觉得它也很烦人.


小智 7

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

/// <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);
    }
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

private UnityContainer _container;
...
_container.RegisterInstance(instance, new LifeTimeManager());
Run Code Online (Sandbox Code Playgroud)

问题是ContainerControlledLifetimeManager的基类期望SynchronizedSetValue通过base.GetValue执行monitor.Enter(),但是ContainerControlledLifetimeManager类无法执行此操作(显然它的开发人员没有启用'break at exception') .

问候,科恩