2010-05-20 10 views
64

Il contenitore di input delle dipendenze di Unity presenta un problema noto in cui SynchronizedLifetimeManager causa spesso il metodo Monitor.Exit per generare una SynchronizationLockException, che viene quindi catturata e ignorata. Questo è un problema per me perché mi piace eseguire il debug con il set di Visual Studio per interrompere qualsiasi eccezione generata, quindi ogni volta che si avvia l'applicazione, sto interrompendo questa eccezione più volte senza motivo.Si può fare Unity per non lanciare SynchronizationLockException tutto il tempo?

Come posso evitare che venga generata questa eccezione?

Ovunque questo problema sia menzionato altrove sul Web, il consiglio di solito comporta la modifica delle impostazioni del debugger per ignorarlo. Questo è come andare dal dottore e dire: "Dottore, dottore, mi fa male il braccio quando lo sollevo", per dire "Bene, smettila di sollevarlo". Sto cercando una soluzione che interrompa l'eccezione che viene lanciata in primo luogo.

L'eccezione si verifica nel metodo SetValue perché presuppone che GetValue sia stato chiamato per primo, dove viene chiamato Monitor.Enter. Tuttavia, le classi LifetimeStrategy e UnityDefaultBehaviorExtension chiamano regolarmente SetValue senza chiamare GetValue.

Preferisco non dover modificare il codice sorgente e mantenere la mia versione di Unity, quindi spero in una soluzione in cui posso aggiungere una combinazione di estensioni, politiche o strategie al contenitore che garantirà che, se il gestore della durata è un SynchronizedLifetimeManager, GetValue viene sempre chiamato prima di qualsiasi altra cosa.

risposta

38

Sono sicuro che ci sono molti modi in cui il codice potrebbe chiamare SynchronizedLifetimeManager, o un discendente come ContainerControlledLifetimeManager, ma in particolare c'erano due scenari che mi causavano problemi.

Il primo è stato il mio errore - stavo usando il constructor injection per fornire un riferimento al contenitore, e in quel costruttore stavo anche aggiungendo la nuova istanza della classe al contenitore per un uso futuro. Questo approccio a ritroso ha avuto l'effetto di cambiare il gestore della vita da Transient a ContainerControlled in modo che l'oggetto Unity chiamato GetValue non fosse lo stesso oggetto su cui è stato impostato SetValue. La lezione appresa è non fare nulla durante il build che potrebbe cambiare il lifetime manager di un oggetto.

Il secondo scenario era che ogni volta che RegisterInstance viene chiamato, UnityDefaultBehaviorExtension chiama SetValue senza chiamare GetValue per primo. Fortunatamente, Unity è abbastanza estensibile da poter risolvere il problema con sufficiente malumore.

Inizia con una nuova estensione comportamento come questo:

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

allora avete bisogno di un modo per sostituire il comportamento predefinito. L'unità non dispone di un metodo per rimuovere un'estensione specifica, in modo da avere per rimuovere tutto e mettere gli altri interni nel nuovo:

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

noti che UnityClearBuildPlanStrategies? RemoveAllExtensions cancella fuori tutti liste interne del contenitore di politiche e strategie ad eccezione di uno, così ho dovuto usare un altro interno al fine di evitare l'inserimento di duplicati quando ho ripristinato le estensioni predefinite:

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

Ora si può tranquillamente utilizzare registerInstance senza paura di essere guidato sull'orlo della pazzia. Giusto per essere sicuri, ecco alcuni test: soluzione

[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 { } 
+0

Stiamo utilizzando Enterprise Library Logger ma non Unity e abbiamo lo stesso problema dal momento che il logger utilizza internamente Unity. Posso applicare quella correzione senza Unity? –

+0

@DmitryGusarov Non l'ho provato, ma se riesci a ottenere un riferimento all'istanza 'UnityContainer' che EntLib usa internamente, dovresti essere in grado di usare il metodo' InstallCoreExtensions' su di esso. Dovresti controllare che EntLib non aggiunga alcuna sua estensione - se lo fa, dovrai aggiungerli di nuovo dopo "InstallCoreExtensions" rimuove tutto. –

+4

NON usare questa soluzione con l'ultima versione di Unity, crea loop infiniti. – Softlion

-8

Questo potrebbe aiutare:

  • Vai a Debug -> Eccezioni ...
  • Trova le eccezioni che davvero sconvolto come SynchronizationLockException

Voila.

+3

Questo è ciò che intendevo dire, "il consiglio di solito comporta la modifica delle impostazioni del debugger per ignorarlo." Non voglio schiacciare SynchronizationLockExceptions che potrebbero causare bug nel mio codice. –

10

La risposta alla tua domanda è purtroppo no. Ho seguito questo con il team di sviluppo qui presso il gruppo di pratiche Microsoft modelli & (ero il responsabile fino a poco tempo fa) e abbiamo avuto questo come un bug da considerare per EntLib 5.0. Abbiamo fatto alcune indagini e siamo giunti alla conclusione che ciò è stato causato da alcune interazioni inaspettate tra il nostro codice e il debugger. Abbiamo preso in considerazione una correzione, ma questo si è rivelato più complesso del codice esistente. Alla fine questo ha avuto la priorità sotto altre cose e non ha fatto il bar per 5.

Mi dispiace, non ho una risposta migliore per voi. Se c'è qualche consolazione, lo trovo anche irritante.

+1

Grazie per la risposta dalla bocca del cavallo! Fortunatamente, Unity è abbastanza estensibile da consentirmi di risolvere il problema: vedi la mia [risposta] (http://stackoverflow.com/questions/2873767/can-unity-be-made-to-not-throw-synchronizationlockexception -tutti-the-time/3153585 # 3153585). –

+0

Ade ha perfettamente sintetizzato la decisione presa dal team EntLib al momento dello sviluppo di EntLib5.0. Se questo continua a essere un fattore irritante per molti, si prega di suggerirlo sul nostro sito di uservoice e votare per esso. Lo considereremo durante la pianificazione di vNext di Unity e EntLib. –

4

di Rory è grande - grazie. Risolto un problema che mi infastidisce ogni giorno! ho fatto alcuni piccoli aggiustamenti alla soluzione di Rory in modo che gestisce tutto ciò che le estensioni sono registrate (nel mio caso ho avuto una proroga di WPF Prism/Composito) ..

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

Ricordarsi di cancellare le strategie del piano di build - 'container.AddExtension (new UnityClearBuildPlanStrategies());' –

1

Attenzione a un errore nella risposta di Zubin Appoo: non c'è UnityClearBuildPlanStrategie mancanti nel suo codice.

Il frammento di codice giusto è:

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

Questo snippet di codice è identico alla risposta di Zubin e non include la chiamata a UnityClearBuildPlanStrategies() –

+0

Hai ragione, corretto –

7

Io uso questo breve soluzione:

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

e usare in questo modo:

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

il problema è che la classe di base di ContainerControlledLifetimeManager si aspetta un SynchronizedSetValue per fare un monitor.Enter() tramite la base.GetValue, ho la classe ContainerControlledLifetimeManager non riesce a farlo (apparentemente i suoi sviluppatori non hanno abilitato l'opzione "break at exception"?).

saluti, Koen

+0

Questo è un buona soluzione per Silverlight! – Kit

+0

Come posso introdurre questa correzione nell'applicazione? Non capisco ... Dovrei registrare LifeTimeManager da qualche parte? Non stiamo usando Unity ... Solo questo Logger ... –

12

Fixed nell'ultima versione dell'Unità (2.1.505.2). Scaricalo tramite NuGet.

0

Unity 2.1 - August 2012 update fix the bug

  1. Affrontare un problema di sicurezza filo: http://unity.codeplex.com/discussions/328841

  2. migliorare l'esperienza di debug su System.Threading.SynchronizationLockException: https://entlib.uservoice.com/forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep

  3. Migliorare esperienza debugging attraverso meglio di messaggistica di errore quando un tipo non può essere caricato: http://unity.codeplex.com/workitem/9223

  4. Sostenere uno scenario di eseguire un accumulo() su un'istanza esistente di una classe che non fa avere un costruttore pubblico: http://unity.codeplex.com/workitem/9460

per rendere l'esperienza di aggiornamento più semplice possibile per gli utenti e per evitare il nee d per i reindirizzamenti dell'associazione degli assiemi, abbiamo scelto di incrementare solo la versione del file di assiemi, non la versione dell'assembly .NET.

Problemi correlati