64 votes

L'Unité peut être faite de ne pas jeter SynchronizationLockException tout le temps?

L'Unité conteneur d'injection de dépendance a ce qui semble être largement un problème connu où la SynchronizedLifetimeManager sont souvent la cause de la Surveiller.La sortie de la méthode de jeter une SynchronizationLockException qui est ensuite capturé et ignoré. C'est un problème pour moi parce que je tiens à déboguer avec Visual Studio définie sur arrêt sur toute la levée d'une exception, de sorte que chaque fois que mon application démarre, je suis en rupture sur cette exception à plusieurs reprises pour aucune raison.

Comment puis-je éviter cette exception d'être jeté?

Partout où cela est mentionné ailleurs sur le web, de l'avis implique généralement de changer les paramètres du débogueur de l'ignorer. Cela s'apparente à aller chez le médecin et en disant: "docteur, Docteur, mon bras me fait quand je lève," a dit, "eh Bien, arrêter de les élever." Je suis à la recherche d'une solution qui s'arrête à l'exception levée dans la première place.

L'exception se produit dans la méthode SetValue parce qu'il fait l'hypothèse que GetValue auront été appelé en premier, où Moniteur.Entrez est appelé. Cependant, la LifetimeStrategy et UnityDefaultBehaviorExtension classes à la fois régulièrement appel SetValue sans appel GetValue.

Je préfère ne pas avoir à modifier le code source et de maintenir ma propre version de l'Unité, donc je suis en espérant une solution où je peux ajouter une combinaison des extensions, des politiques, des stratégies ou le conteneur qui fera en sorte que, si la durée de vie manager est un SynchronizedLifetimeManager, GetValue est toujours appelée avant toute autre chose.

38voto

Rory MacLeod Points 4574

Je suis sûr qu'il ya beaucoup de façons de code pourrait appeler SynchronizedLifetimeManager, ou d'un descendant comme ContainerControlledLifetimeManager, mais il y avait deux scénarios, en particulier, qui ont été me causer des problèmes.

La première était de ma faute - j'ai été en utilisant le constructeur de l'injection de fournir une référence au conteneur, et dans ce constructeur, j'ai été également l'ajout de la nouvelle instance de la classe pour le conteneur pour une utilisation future. Cette rétro approche ayant pour effet de modifier la durée de vie du gestionnaire de Transitoire à ContainerControlled de sorte que l'objet de l'Unité appelée GetValue n'était pas le même objet, qu'il appelle SetValue. La leçon à retenir est de ne rien faire pendant la phase de construction qui pourrait changer la vie de l'objet gestionnaire.

Le deuxième scénario a été que chaque fois RegisterInstance est appelé, UnityDefaultBehaviorExtension appels SetValue sans appel GetValue premier. Heureusement, l'Unité est suffisamment extensible que, avec assez sanglante d'esprit, vous pouvez contourner le problème.

Démarrer avec un nouveau comportement de l'extension comme ceci:

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

Ensuite, vous avez besoin d'un moyen de remplacer le comportement par défaut. L'unité ne dispose pas d'une méthode pour supprimer une extension spécifique, de sorte que vous avez à tout supprimer et mettre les autres extensions de retour à nouveau:

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

Notez que UnityClearBuildPlanStrategies? RemoveAllExtensions efface toutes les conteneur des listes internes de politiques et de stratégies, sauf une, j'ai donc dû utiliser un autre poste pour éviter l'insertion de doublons quand j'ai restauré les extensions par défaut:

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

Maintenant, vous pouvez utiliser en toute sécurité RegisterInstance sans crainte d'être au bord de la folie. Juste pour être sûr, voici quelques 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 { }

12voto

Grigori Melnik Points 2676

Corrigé dans la dernière version de l'Unité (2.1.505.2). L'obtenir via NuGet.

10voto

Ade Miller Points 7750

La réponse à votre question est malheureusement pas. J'ai suivi ce projet avec l'équipe de développement d'ici à Microsoft des modèles et pratiques de groupe (j'étais le dev y mener jusqu'à récemment)et nous avons eu cela comme un bug à prendre en compte pour EntLib 5.0. Nous avons fait quelques recherches, et en vint à la conclusion que c'était causé par certains des interactions inattendues entre notre code et le débogueur. Nous ne considérons un correctif mais cela s'est avéré plus complexe que le code existant. Dans la fin ce que cela a priorité ci-dessous d'autres choses et n'a pas la barre des 5.

Désolé je n'ai pas de meilleure réponse pour vous. Si c'est une consolation, je la trouve trop irritant.

7voto

Koen VV Points 71

J'utilise cette courte 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);
    }
}

et de l'utiliser comme ceci:

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

le problème est que la classe de base de ContainerControlledLifetimeManager s'attend à un SynchronizedSetValue faire un moniteur.Enter() par l'intermédiaire de la base.GetValue, cependant la ContainerControlledLifetimeManager classe ne parvient pas à le faire (apparemment, les développeurs n'ont pas 'casser à l'exception activé?).

en ce qui concerne, Koen

4voto

Zubin Appoo Points 41

Rory la solution de grand - grâce. Résolu un problème qui m'ennuie tous les jours! J'ai fait quelques modifications mineures à Rory de solution pour qu'elle traite quelles que soient les extensions sont enregistrés (dans mon cas, j'ai eu un WPF Prisme/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);
            }
        }
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X