Kan Unity zo gemaakt worden dat SynchronizationLockException niet de hele tijd wordt gegenereerd?

De Unity-afhankelijkheidsinjectiecontainer heeft wat een algemeen bekend probleem lijkt te zijn waarbij de SynchronizedLifetimeManager er vaak voor zorgt dat de Monitor.Exit-methode een SynchronizationLockException genereert die vervolgens wordt opgevangen en genegeerd. Dit is een probleem voor mij omdat ik graag foutopsporing doe met Visual Studio ingesteld om te breken bij elke gegenereerde uitzondering, dus elke keer dat mijn toepassing opstart, verbreek ik deze uitzondering meerdere keren zonder reden.

Hoe kan ik voorkomen dat deze uitzondering wordt gegenereerd?

Waar dit probleem ook elders op het web wordt genoemd, is het advies meestal om de debugger-instellingen te wijzigen om het te negeren. Dit is hetzelfde als naar de dokter gaan en zeggen: “Dokter, dokter, mijn arm doet pijn als ik hem optil”, en te horen krijgen: “Nou, stop met hem op te tillen.” Ik ben op zoek naar een oplossing die voorkomt dat de uitzondering in de eerste plaats wordt gegenereerd.

De uitzondering treedt op in de SetValue-methode omdat deze ervan uitgaat dat GetValue als eerste wordt aangeroepen, waar Monitor.Enter wordt aangeroepen. De klassen LifetimeStrategy en UnityDefaultBehaviorExtension roepen echter regelmatig SetValue aan zonder GetValue aan te roepen.

Ik hoef de broncode liever niet te wijzigen en mijn eigen versie van Unity te onderhouden, dus ik hoop op een oplossing waarbij ik een combinatie van extensies, beleidsregels of strategieën aan de container kan toevoegen die ervoor zullen zorgen dat , als de lifetime manager een SynchronizedLifetimeManager is, wordt GetValue altijd voor alles aangeroepen.


Antwoord 1, autoriteit 100%

Ik weet zeker dat er veel manieren zijn waarop code SynchronizedLifetimeManager kan aanroepen, of een afstammeling zoals ContainerControlledLifetimeManager, maar er waren in het bijzonder twee scenario’s die me problemen gaven.

De eerste was mijn eigen fout – ik gebruikte constructorinjectie om een ​​verwijzing naar de container te leveren, en in die constructor voegde ik ook de nieuwe instantie van de klasse toe aan de container voor toekomstig gebruik. Deze achterwaartse benadering had tot gevolg dat de levensduurmanager veranderde van Transient naar ContainerControlled, zodat het object Unity met de naam GetValue niet hetzelfde object was waarop het SetValue aanriep. De geleerde les is doe tijdens de opbouw niets dat de levensduurmanager van een object zou kunnen veranderen.

Het tweede scenario was dat elke keer dat RegisterInstance wordt aangeroepen, UnityDefaultBehaviorExtension SetValue aanroept zonder eerst GetValue aan te roepen. Gelukkig is Unity zo uitbreidbaar dat je, met genoeg bloederige geest, het probleem kunt omzeilen.

Begin met een nieuwe gedragsextensie zoals deze:

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

Dan heb je een manier nodig om het standaardgedrag te vervangen. Unity heeft geen methode om een ​​specifieke extensie te verwijderen, dus je moet alles verwijderen en de andere extensies er weer in zetten:

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

Merk je op dat UnityClearBuildPlanStrategies? RemoveAllExtensions wist alle interne lijsten met beleidsregels en strategieën van de container op één na, dus ik moest een andere extensie gebruiken om te voorkomen dat ik duplicaten invoeg toen ik de standaardextensies herstelde:

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

Nu kunt u RegisterInstance veilig gebruiken zonder bang te zijn dat u tot waanzin wordt gedreven. Voor de zekerheid zijn hier enkele 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 { }

Antwoord 2, autoriteit 32%

Opgelostin de nieuwste release van Unity (2.1.505.2). Verkrijg het via NuGet.


Antwoord 3, autoriteit 26%

Het antwoord op uw vraag is helaas nee. Ik heb dit opgevolgd met het dev-team hier bij de Microsoft Patterns & oefengroep (ik was daar tot voor kort de dev-leider) en we hadden dit als een bug om te overwegen voor EntLib 5.0. We hebben wat onderzoek gedaan en kwamen tot de conclusie dat dit werd veroorzaakt door enkele onverwachte interacties tussen onze code en de debugger. We hebben wel een fix overwogen, maar deze bleek complexer dan de bestaande code. Uiteindelijk kreeg dit prioriteit onder andere dingen en haalde het niet de lat voor 5.

Sorry dat ik geen beter antwoord voor je heb. Als het een troost mag zijn, ik vind het ook irritant.


Antwoord 4, autoriteit 18%

Ik gebruik deze korte oplossing:

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

en gebruik het als volgt:

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

het probleem is dat de basisklasse van ContainerControlledLifetimeManager verwacht dat een SynchronizedSetValue een monitor.Enter() doet via de base.GetValue, maar de klasse ContainerControlledLifetimeManager doet dit niet (blijkbaar hadden de ontwikkelaars geen ‘break at exception’ ingeschakeld?).

groeten,
Koen


Antwoord 5, autoriteit 11%

Rory’s oplossing is geweldig – bedankt. Een probleem opgelost dat me elke dag irriteert!
Ik heb wat kleine aanpassingen gedaan aan Rory’s oplossing, zodat deze alle geregistreerde extensies aankan (in mijn geval had ik een WPF Prism/Composite-extensie).

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

Antwoord 6, autoriteit 3%

Pas op voor één fout in het antwoord van Zubin Appoo: er ontbreekt UnityClearBuildPlanStrategiesin zijn code.

Het juiste codefragment 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);
   }
}

Antwoord 7

Unity 2.1 – update van augustus 2012 lost de bug op

  1. Een veiligheidsprobleem in een thread oplossen:
    http://unity.codeplex.com/discussions/328841

  2. Verbetering van de foutopsporingservaring op System.Threading.SynchronizationLockException:
    https://entlib.uservoice.com /forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep

  3. Verbetering van de foutopsporingservaring door betere foutmeldingen wanneer een type niet kan worden geladen:
    http://unity.codeplex.com/workitem/9223

  4. Ondersteuning van een scenario voor het uitvoeren van een BuildUp() op een bestaande instantie van een klasse die geen openbare constructor heeft:
    http://unity.codeplex.com/workitem/9460

Om de update-ervaring voor gebruikers zo eenvoudig mogelijk te maken en om de noodzaak van omleidingen voor assembly-bindingen te voorkomen, hebben we ervoor gekozen om alleen de versie van het assembly-bestand te verhogen, niet de .NET-assembly-versie.


Antwoord 8

Dit kan je helpen:

  • Ga naar Foutopsporing -> Uitzonderingen…
  • Zoek de uitzonderingen die je echt van streek maken, zoals SynchronizationLockException

Voila.

Other episodes