wake-up-neo.com

Kann Unity so eingestellt werden, dass SynchronizationLockException nicht die ganze Zeit ausgelöst wird?

Der Unity-Abhängigkeitsinjektionscontainer weist ein bekanntes Problem auf, bei dem der SynchronizedLifetimeManager häufig die Monitor.Exit-Methode veranlasst, eine SynchronizationLockException auszulösen, die dann abgefangen und ignoriert wird. Dies ist ein Problem für mich, da ich gerne mit Visual Studio debuggen möchte, um bei jeder ausgelösten Ausnahme zu brechen, sodass ich bei jedem Start meiner Anwendung diese Ausnahme mehrmals ohne Grund brechen kann.

Wie kann ich verhindern, dass diese Ausnahme ausgelöst wird?

Wenn dieses Problem an einer anderen Stelle im Internet erwähnt wird, müssen Sie in der Regel die Debugger-Einstellungen ändern, um es zu ignorieren. Das ist so, als würde man zum Arzt gehen und sagen: "Doktor, Doktor, mein Arm tut weh, wenn ich ihn anhebe." Ich suche nach einer Lösung, die verhindert, dass die Ausnahme überhaupt erst ausgelöst wird.

Die Ausnahme tritt in der SetValue-Methode auf, da davon ausgegangen wird, dass GetValue zuerst aufgerufen wurde, wobei Monitor.Enter aufgerufen wird. Die Klassen LifetimeStrategy und UnityDefaultBehaviorExtension rufen jedoch regelmäßig SetValue auf, ohne GetValue aufzurufen.

Ich möchte lieber nicht den Quellcode ändern und meine eigene Version von Unity warten müssen, also hoffe ich auf eine Lösung, bei der ich dem Container eine Kombination von Erweiterungen, Richtlinien oder Strategien hinzufügen kann, die dies sicherstellen, wenn die Der Lifetime Manager ist ein SynchronizedLifetimeManager, GetValue wird immer vor allem anderen aufgerufen.

64
Rory MacLeod

Ich bin sicher, es gibt viele Möglichkeiten, wie Code SynchronizedLifetimeManager oder einen Nachkomme wie ContainerControlledLifetimeManager aufrufen kann, aber es gab insbesondere zwei Szenarien, die mir Probleme bereiteten.

Der erste war mein eigener Fehler - ich verwendete die Konstruktorinjektion, um einen Verweis auf den Container bereitzustellen, und in diesem Konstruktor fügte ich auch die neue Instanz der Klasse dem Container für die zukünftige Verwendung hinzu. Dieser Rückwärtsansatz hatte den Effekt, dass der Lebensdauer-Manager von Transient in ContainerControlled geändert wurde, so dass das Objekt Unity, das GetValue genannt wurde, nicht das gleiche Objekt war, für das SetValue aufgerufen wurde. Die Lektion ist Sie sollten während des Aufbaus nichts tun, was den Lifetime Manager eines Objekts ändern könnte.

Das zweite Szenario war, dass UnityDefaultBehaviorExtension bei jedem Aufruf von RegisterInstance SetValue aufruft, ohne vorher GetValue aufzurufen. Glücklicherweise ist Unity so erweiterbar, dass Sie mit genug blutigem Geist das Problem umgehen können.

Beginnen Sie mit einer neuen Verhaltenserweiterung wie folgt:

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

Dann müssen Sie das Standardverhalten ersetzen. Unity verfügt nicht über eine Methode zum Entfernen einer bestimmten Erweiterung. Sie müssen also alles entfernen und die anderen Erweiterungen wieder einsetzen:

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

Beachten Sie, dass UnityClearBuildPlanStrategies? RemoveAllExtensions löscht alle internen Richtlinien- und Strategienlisten des Containers bis auf eine. Daher musste ich eine andere Erweiterung verwenden, um das Einfügen von Duplikaten zu vermeiden, wenn die Standarderweiterungen wiederhergestellt wurden:

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

Jetzt können Sie RegisterInstance ohne Angst vor dem Wahnsinn treiben. Nur um sicher zu sein, hier sind einige 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 { }
38
Rory MacLeod

Behoben in der neuesten Version von Unity (2.1.505.2). Holen Sie es über NuGet.

12
Grigori Melnik

Die Antwort auf Ihre Frage lautet leider nein. Ich habe dies mit dem Entwicklerteam hier bei der Gruppe "Patterns & Practices" von Microsoft (ich war dort bis vor kurzem als Dev-Lead) nachgegangen, und wir hatten dies als einen Fehler für EntLib 5.0. Wir haben einige Nachforschungen angestellt und kamen zu dem Schluss, dass dies auf unerwartete Interaktionen zwischen unserem Code und dem Debugger zurückzuführen ist. Wir haben eine Korrektur in Betracht gezogen, die sich jedoch als komplexer als der vorhandene Code herausstellte. Am Ende wurde dies unter anderen Dingen priorisiert und nicht für 5 die Latte gesetzt.

Entschuldigung, ich habe keine bessere Antwort für Sie. Wenn es irgendein Trost ist, finde ich es auch irritierend.

10
Ade Miller

Ich verwende diese kurze Lösung:

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

und benutze es so:

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

das Problem ist, dass die Basisklasse von ContainerControlledLifetimeManager erwartet, dass ein SynchronizedSetValue einen monitor.Enter () über die base.GetValue ausführt. Die ContainerControlledLifetimeManager-Klasse kann dies jedoch nicht tun (anscheinend war bei den Entwicklern die Option 'break at exception' nicht aktiviert?). .

grüße Koen

7
Koen VV

Rorys Lösung ist großartig - danke. Ich habe ein Problem gelöst, das mich jeden Tag nervt! Ich habe einige kleine Änderungen an Rorys Lösung vorgenommen, damit alle registrierten Erweiterungen gehandhabt werden können (in meinem Fall hatte ich eine WPF-Erweiterung Prism/Composite). 

    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);
            }
        }
    }
4
Zubin Appoo

Hüten Sie sich vor einem Fehler in der Antwort von Zubin Appoo: In seinem Code fehlen UnityClearBuildPlanStrategies

Das richtige Code-Snippet lautet:

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

Unity 2.1 - Update vom August 2012 behebt den Fehler

  1. Lösung eines Thread-Sicherheitsproblems: http://unity.codeplex.com/discussions/328841

  2. Verbesserung der Debugging-Erfahrung bei System.Threading.SynchronizationLockException: https://entlib.uservoice.com/forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep

  3. Verbessern der Debugging-Erfahrung durch bessere Fehlermeldung, wenn ein Typ nicht geladen werden kann: http://unity.codeplex.com/workitem/9223

  4. Unterstützung eines Szenarios zum Ausführen eines BuildUp () für eine vorhandene Instanz einer Klasse ohne öffentlichen Konstruktor: http://unity.codeplex.com/workitem/9460

Um das Update für Benutzer so einfach wie möglich zu gestalten und um die Notwendigkeit für Assembly-Bindungsumleitungen zu vermeiden, haben wir uns dazu entschieden, nur die Assembly-Dateiversion zu erhöhen, nicht die .NET Assembly-Version.

0
huoxudong125