c# - Unity 可以一直不抛出 SynchronizationLockException 吗?

标签 c# unity-container enterprise-library

Unity 依赖项注入(inject)容器有一个似乎广为人知的问题,即 SynchronizedLifetimeManager 经常会导致 Monitor.Exit 方法抛出 SynchronizationLockException,该异常随后会被捕获并忽略。这对我来说是个问题,因为我喜欢将 Visual Studio 设置为在遇到任何抛出的异常时中断,因此每次我的应用程序启动时,我都会无缘无故地多次中断此异常。

如何防止抛出此异常?

只要网络上其他地方提到这个问题,建议通常涉及更改调试器设置以忽略它。这就好比去看医生说,“医生,医生,我的胳膊一抬起来就疼”,却被告知,“好吧,别再抬了。”我正在寻找一种解决方案,首先阻止抛出异常。

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

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

最佳答案

我确信代码可以通过多种方式调用 SynchronizedLifetimeManager 或类似 ContainerControlledLifetimeManager 的后代,但有两种情况特别给我带来了问题。

首先是我自己的错——我使用构造函数注入(inject)来提供对容器的引用,并且在该构造函数中我还将类的新实例添加到容器中以供将来使用。这种向后的方法具有将生命周期管理器从 Transient 更改为 ContainerControlled 的效果,因此 Unity 在其上调用 GetValue 的对象与在其上调用 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();
        }
    }
}

那么您需要一种方法来替换默认行为。 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 { }

关于c# - Unity 可以一直不抛出 SynchronizationLockException 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2873767/

相关文章:

c# - Neo4j .NET 客户端执行字符串密码查询

c# - DbType 等同于 SqlDbType.Bit

c# - 使用 Enterprise Library 5.0 Logging Block 打包日志

wpf - 我可以在 View code behind 中使用 Unity CContainer 吗? (MVVM)

asp.net-mvc - ASP.NET MVC 和 Unity 1.2 容器问题

c# - AppDomain AssemblyResolve 事件请求 Microsoft.Practices.EnterpriseLibrary.Common.resources

c# - 在c#中将灰度图像转换为黑白图像

c# - 如何通过 SignalR 传递复杂对象?

c# - 如何在具有 AllowPartiallyTrustedCallersAttribute 的库程序集中的 .NET 4 中实现 Exception.GetObjectData?

c# - 当我从 Ioc 配置中使用时,我收到错误信息(无法在泛型类型或方法中用作类型参数 'TTo')