it-swarm.com.de

Unity 2.0 und Umgang mit IDisposable-Typen (insbesondere mit PerThreadLifetimeManager)

Ich weiß, dass eine ähnliche Frage mehrmals gestellt wurde (zum Beispiel: hier , hier , hier und hier ), aber für frühere Versionen von Unity war die Antwort abhängig auf verwendete LifetimeManager Klasse.

Dokumentation sagt:

Unity verwendet bestimmte Typen, die von der LifetimeManager-Basisklasse erben (zusammen als Lifetime Manager bezeichnet), um zu steuern, wie Verweise auf Objektinstanzen gespeichert werden und wie der Container über diese Instanzen verfügt.

Ok, hört sich gut an, also habe ich beschlossen, die Implementierung von Build-in-Lifetime-Managern zu überprüfen. Meine Schlussfolgerung:

  • TransientLifetimeManager - keine Entsorgung. Der Container löst nur die Instanz auf und verfolgt sie nicht. Der aufrufende Code ist für die Entsorgung der Instanz verantwortlich.
  • ContainerControlledLifetimeManager - Entsorgt die Instanz, wenn der Lifetime Manager entsorgt wird (= wenn der Container entsorgt wird). Bietet eine Singleton-Instanz, die von allen Containern in hiearchy gemeinsam genutzt wird.
  • HierarchicalLifetimeManager - leitet das Verhalten von ContainerControlledLifetimeManager ab. Es liefert "Singleton" -Instanzen pro Container in hiearchy (Subcontainer).
  • ExternallyControlledLifetimeManager - keine Entsorgung. Richtiges Verhalten, da der Container nicht Eigentümer der Instanz ist.
  • PerResolveLifetimeManager - keine Entsorgung. Es ist im Allgemeinen dasselbe wie TransientLifetimeManager, ermöglicht jedoch die Wiederverwendung der Instanz für die Abhängigkeitsinjektion beim Auflösen des gesamten Objektgraphen.
  • PerThreadLifetimeManager - keine Entsorgungshandhabung wie auch in MSDN beschrieben. Wer ist für die Entsorgung verantwortlich?

Die Implementierung der eingebauten PerThreadLifetimeManager ist:

public class PerThreadLifetimeManager : LifetimeManager
{
    private readonly Guid key = Guid.NewGuid();
    [ThreadStatic]
    private static Dictionary<Guid, object> values;

    private static void EnsureValues()
    {
        if (values == null)
        {
            values = new Dictionary<Guid, object>();
        }
    }

    public override object GetValue()
    {
        object result;
        EnsureValues();
        values.TryGetValue(this.key, out result);
        return result;
    }

    public override void RemoveValue()
    { }

    public override void SetValue(object newValue)
    {
        EnsureValues();
        values[this.key] = newValue;
    }
}

Das Entsorgen von Containern entsorgt also keine mit diesem Lifetime Manager erstellten Einweginstanzen. Durch die Thread-Vervollständigung werden diese Instanzen ebenfalls nicht freigegeben. Wer ist also für die Freigabe von Instanzen verantwortlich?

Ich habe versucht, die gelöste Instanz manuell im Code zu entsorgen, und ein anderes Problem festgestellt. Ich kann den Ofen nicht abreißen. RemoveValue von Lifetime Manager ist leer - Sobald die Instanz erstellt wurde, kann sie nicht mehr aus dem statischen Thread-Wörterbuch entfernt werden (ich bin auch verdächtig, dass die Methode TearDown nichts bewirkt). Wenn Sie also Resolve aufrufen, nachdem Sie die Instanz entsorgt haben, erhalten Sie die entsorgte Instanz. Ich denke, das kann ein ziemlich großes Problem sein, wenn man diesen Lifetime Manager mit Threads aus dem Thread-Pool verwendet.

Wie man diesen Lebenszeitmanager richtig benutzt?

Darüber hinaus wird diese Implementierung häufig in benutzerdefinierten Lifetime-Managern wie PerCallContext, PerHttpRequest, PerAspNetSession, PerWcfCall usw. wiederverwendet. Nur das statische Thread-Wörterbuch wird durch ein anderes Konstrukt ersetzt.

Verstehe ich es auch richtig, dass der Umgang mit Einwegobjekten vom Lifetime Manager abhängt? Der Anwendungscode ist also abhängig vom verwendeten Lifetime Manager.

Ich habe gelesen, dass in anderen IoC-Containern, die sich mit temporären Einwegobjekten befassen, Subcontainer verwendet werden, ich jedoch kein Beispiel für Unity gefunden habe - dies könnte wahrscheinlich mit Subcontainern mit lokalem Gültigkeitsbereich und HiearchicalLifetimeManager geschehen, aber ich bin mir nicht sicher, wie ich das tun soll.

40
Ladislav Mrnka

Es gibt nur wenige Umstände, unter denen Unity über eine Instanz verfügt. Es wird wirklich nicht unterstützt. Meine Lösung war eine benutzerdefinierte Erweiterung, um dies zu erreichen - http://www.neovolve.com/2010/06/18/unity-extension-for-disposing-build-trees-on-teardown/

5
Rory Primrose

Betrachtet man den Unity 2.0-Quellcode, riecht es so, als würden die LifetimeManager verwendet, um Objekte auf unterschiedliche Weise im Gültigkeitsbereich zu halten, damit der Garbage Collector sie nicht entfernt. Mit dem PerThreadLifetimeManager wird beispielsweise ThreadStatic verwendet, um einen Verweis auf jedes Objekt mit der Lebensdauer dieses bestimmten Threads zu speichern. Es wird jedoch nicht Entsorgen aufgerufen, bis der Container entsorgt ist.

Es gibt ein LifetimeContainer-Objekt, das verwendet wird, um alle erstellten Instanzen zu speichern. Es wird dann entsorgt, wenn der UnityContainer entsorgt wird (wodurch wiederum alle darin enthaltenen IDisposables in umgekehrter chronologischer Reihenfolge entsorgt werden).

BEARBEITEN: Bei näherer Betrachtung enthält der LifetimeContainer nur LifetimeManager (daher der Name "Lifetime" -Container). Wenn es also entsorgt wird, entsorgt es nur die lebenslangen Manager. (und wir stehen vor dem Problem, das bereits diskutiert wurde).

2

wäre es eine praktikable Lösung, das Ereignis HttpContext.Current.ApplicationInstance.EndRequest zu verwenden, um eine Verknüpfung zum Ende der Anforderung herzustellen und dann das in diesem lebenslangen Manager gespeicherte Objekt zu löschen? wie so:

public HttpContextLifetimeManager()
{
    HttpContext.Current.ApplicationInstance.EndRequest += (sender, e) => {
        Dispose();
    };
}

public override void RemoveValue()
{
    var value = GetValue();
    IDisposable disposableValue = value as IDisposable;

    if (disposableValue != null) {
        disposableValue.Dispose();
    }
    HttpContext.Current.Items.Remove(ItemName);
}

public void Dispose()
{
    RemoveValue();
}

sie müssen keinen untergeordneten Container wie die andere Lösung verwenden, und der Code, der zum Entsorgen der Objekte verwendet wird, befindet sich weiterhin im Lebensdauermanager, wie er sollte.

1
Francois Joly

Ich bin vor kurzem selbst auf dieses Problem gestoßen, als ich Unity in meine Anwendung einfließen ließ. Die Lösungen, die ich hier bei Stack Overflow und anderswo online gefunden habe, schienen meiner Meinung nach das Problem nicht zufriedenstellend anzugehen.

Wenn Sie Unity nicht verwenden, weisen IDisposable-Instanzen ein gut bekanntes Verwendungsmuster auf:

  1. Platzieren Sie sie in einem Bereich, der kleiner als eine Funktion ist, in einem using-Block, um sie "kostenlos" zu entsorgen.

  2. Implementieren Sie bei der Erstellung für ein Instanzmitglied einer Klasse IDisposable in der Klasse und setzen Sie die Bereinigung in Dispose().

  3. Tun Sie nichts, wenn Sie an den Konstruktor einer Klasse übergeben werden, da sich die IDisposable-Instanz an einer anderen Stelle befindet.

Die Einheit verwirrt die Dinge, denn wenn die Abhängigkeitsinjektion richtig durchgeführt wird, geht der obige Fall Nr. 2 verloren. Alle Abhängigkeiten sollten injiziert werden, was bedeutet, dass im Wesentlichen keine Klassen Eigentümer der zu erstellenden IDisposable-Instanzen sind. Es bietet jedoch auch keine Möglichkeit, an die IDisposables zu gelangen, die während eines Resolve()-Aufrufs erstellt wurden, sodass using-Blöcke anscheinend nicht verwendet werden können. Welche Option bleibt noch?

Meine Schlussfolgerung ist, dass die Resolve()-Schnittstelle im Wesentlichen falsch ist. Die Rückgabe nur des angeforderten Typs und von undichten Objekten, die eine spezielle Behandlung wie IDisposable erfordern, kann nicht korrekt sein.

Als Antwort habe ich die Erweiterung IDisposableTrackingExtension für Unity geschrieben, die IDisposable-Instanzen protokolliert, die während einer Typauflösung erstellt wurden, und ein Disposable-Wrapperobjekt zurückgibt, das eine Instanz des angeforderten Typs und alle IDisposable-Abhängigkeiten aus dem Objektdiagramm enthält.

Mit dieser Erweiterung sieht die Typauflösung folgendermaßen aus (hier unter Verwendung einer Factory, da Ihre Geschäftsklassen IUnityContainer niemals als Abhängigkeit verwenden sollten):

public class SomeTypeFactory
{
  // ... take IUnityContainer as a dependency and save it

  IDependencyDisposer< SomeType > Create()
  {
    return this.unity.ResolveForDisposal< SomeType >();
  }
}

public class BusinessClass
{
  // ... take SomeTypeFactory as a dependency and save it

  public void AfunctionThatCreatesSomeTypeDynamically()
  {
    using ( var wrapper = this.someTypeFactory.Create() )
    {
      SomeType subject = wrapper.Subject;
      // ... do stuff
    }
  }
}

Dadurch werden die IDisposable-Verwendungsmuster Nr. 1 und Nr. 3 von oben in Einklang gebracht. Normale Klassen verwenden die Abhängigkeitsinjektion. Sie besitzen keine injizierten IDisposables, deshalb entsorgen sie sie nicht. Klassen, die eine Typauflösung (über Fabriken) durchführen, weil sie dynamisch erstellte Objekte benötigen. Diese Klassen sind die Eigentümer, und diese Erweiterung bietet die Möglichkeit zum Verwalten von Entsorgungsbereichen.

0
Kurt Hutchinson