Programing

Unity가 항상 SynchronizationLockException을 발생시키지 않도록 만들 수 있습니까?

lottogame 2020. 11. 22. 18:53
반응형

Unity가 항상 SynchronizationLockException을 발생시키지 않도록 만들 수 있습니까?


Unity 종속성 주입 컨테이너에는 SynchronizedLifetimeManager가 종종 Monitor.Exit 메서드가 SynchronizationLockException을 throw하는 원인이되는 널리 알려진 문제로 보이는 문제가 있습니다. throw 된 예외를 중단하도록 설정된 Visual Studio로 디버깅하는 것을 좋아하기 때문에 이것은 저에게 문제입니다. 그래서 내 응용 프로그램이 시작될 때마다 이유없이이 예외를 여러 번 중단합니다.

이 예외가 발생하지 않도록하려면 어떻게해야합니까?

이 문제가 웹의 다른 곳에서 언급 될 때마다 일반적으로이를 무시하도록 디버거 설정을 변경하는 것이 좋습니다. 이것은 의사에게 가서 "의사 님, 의사 님, 팔을 올리면 팔이 아파요."라고 말하는 것과 비슷합니다. "글쎄요, 올리지 마세요." 처음에 예외가 발생하지 않도록하는 솔루션을 찾고 있습니다.

SetValue 메서드에서 예외가 발생하는 이유는 Monitor.Enter가 호출되는 GetValue가 먼저 호출 될 것이라고 가정하기 때문입니다. 그러나 LifetimeStrategy 및 UnityDefaultBehaviorExtension 클래스는 모두 GetValue를 호출하지 않고 정기적으로 SetValue를 호출합니다.

소스 코드를 변경하고 고유 한 Unity 버전을 유지 관리 할 필요가 없으므로 컨테이너에 확장, 정책 또는 전략의 조합을 추가 할 수있는 솔루션을 찾고 있습니다. 수명 관리자는 SynchronizedLifetimeManager이며 GetValue는 항상 다른 것보다 먼저 호출됩니다.


코드가 SynchronizedLifetimeManager 또는 ContainerControlledLifetimeManager와 같은 하위 항목을 호출 할 수있는 방법은 여러 가지가 있지만 특히 문제를 일으키는 두 가지 시나리오가있었습니다.

첫 번째는 내 잘못이었습니다. 생성자 주입을 사용하여 컨테이너에 대한 참조를 제공했고 해당 생성자에서 나중에 사용할 수 있도록 클래스의 새 인스턴스를 컨테이너에 추가했습니다. 이 역방향 접근 방식은 수명 관리자를 Transient에서 ContainerControlled로 변경하여 GetValue를 호출 한 Unity 개체가 SetValue를 호출 한 개체와 동일하지 않도록하는 효과가있었습니다. 배운 교훈은 빌드 업 중에 객체의 수명 관리자를 변경할 수있는 어떤 작업도하지 않는다는 것입니다.

두 번째 시나리오는 RegisterInstance가 호출 될 때마다 UnityDefaultBehaviorExtension이 GetValue를 먼저 호출하지 않고 SetValue를 호출한다는 것입니다. 운 좋게도 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 { }

Unity의 최신 릴리스 (2.1.505.2)에서 수정 되었습니다. NuGet을 통해 가져옵니다.


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.


I use this short solution:

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

and use it like this:

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

the problem is that the base class of ContainerControlledLifetimeManager expects a SynchronizedSetValue to do a monitor.Enter() via the base.GetValue, however the ContainerControlledLifetimeManager class fails to do this (apparently its developers didn't have 'break at exception' enabled?).

regards, Koen


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

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

Unity 2.1 - August 2012 update fix the bug

  1. Addressing a thread safety issue: http://unity.codeplex.com/discussions/328841

  2. Improving debugging experience on System.Threading.SynchronizationLockException: https://entlib.uservoice.com/forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep

  3. Improving debugging experience through better error messaging when a type cannot be loaded: http://unity.codeplex.com/workitem/9223

  4. Supporting a scenario of performing a BuildUp() on an existing instance of a class that doesn’t have a public constructor: http://unity.codeplex.com/workitem/9460

To make the update experience as simple as possible for users and to avoid the need for assembly binding redirects, we chose to only increment the assembly file version, not the .NET assembly version.


This might help you:

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

Voila.

참고URL : https://stackoverflow.com/questions/2873767/can-unity-be-made-to-not-throw-synchronizationlockexception-all-the-time

반응형