Laden...

Auf produktivem IIS wird Singleton mehrmals erzeugt

Erstellt von Christoph K. vor 6 Jahren Letzter Beitrag vor 6 Jahren 3.486 Views
Christoph K. Themenstarter:in
821 Beiträge seit 2009
vor 6 Jahren
Auf produktivem IIS wird Singleton mehrmals erzeugt

Hallo zusammen,

ich habe in meine Web-Projekt eine threadsichere Singleton implementiert.

 private static object syncRoot = new Object();
        public static RamboService GetInstance(string pageDbPathForFastData, string pageDbPathForSlowData)
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    logger.InfoFormat("Creating static RamboService in threaed {0}", Thread.CurrentThread.ManagedThreadId);
                    instance = new RamboService(pageDbPathForFastData, pageDbPathForSlowData);

                }
            }

            return instance;
        }

In der Entwicklungsumgebung (IIS-Express) wird diese auch genau einmal erzeugt, wie es sein soll.

Lade ich das ganze jedoch auf dem produktiven IIS hoch, wird die Singleton mehrmals von verschiedenen Threads erzeugt.

Wie kann ich das umgehen? bzw. Woran liegt es ?

16.835 Beiträge seit 2008
vor 6 Jahren

Singleton haben in 99% der Fälle in Web Anwendungen nichts zu suchen.
Warum verwendest Du keine Dependency Injection, wie man es machen soll?

Webanwendungen erzeugen pro Request einen eigenen Thread. Nur weil Du einen Singleton hast, bist Du noch lange nicht Thread-sicher. Das sind zwei paar Stiefel.

Christoph K. Themenstarter:in
821 Beiträge seit 2009
vor 6 Jahren

Wie würdest du denn hier mit Dependency Injection arbeiten ?

16.835 Beiträge seit 2008
vor 6 Jahren

Mh? Ich versteh die Frage nicht...

Christoph K. Themenstarter:in
821 Beiträge seit 2009
vor 6 Jahren

Wo muss ich ansetzen. Ist für mich Neuland.
Brauch ich hierfür ein Framework oder ähnliches?

16.835 Beiträge seit 2008
vor 6 Jahren

Einfach mal in die Doku schauen. Dafür ist sie da.

ASP.NET MVC 4 Dependency Injection

oder für ASP.NET Core
Introduction to Dependency Injection in ASP.NET Core

Christoph K. Themenstarter:in
821 Beiträge seit 2009
vor 6 Jahren

Okay, ich habe das jetzt mal so weit nachprogrammiert.

Mein Problem ist, dass ich jetzt immer, wenn ich einen Typ anfordere eine neue Instanz dieses Typen bekomme. Das Problem mit der Abhängigkeit (welches ich ja eigentlich gar nicht hatte) ist damit gelöst, aber wie bekomme ich denn mit DI immer die gleich Instanz eines Typen ?

Oder muss ich jetzt innerhalb des Typs wieder ein Singleton implementieren ?

16.835 Beiträge seit 2008
vor 6 Jahren

Wieso liest Du nicht einfach die Doku? Keine Lust? Arbeit auf uns abwälzen?
Damit wären alle Deine Fragen gelöst.

Mein Problem ist, dass ich jetzt immer, wenn ich einen Typ anfordere eine neue Instanz dieses Typen bekomme.

Ist prinzipiell auch das richtige Vorgehen in Web Anwendungen.

Warum denkst Du denn, dass Du einen Singleton oder immer die gleiche Instanz brauchst?
Singletons sind prinzipiell ein Hinweis auf ein Architekturfehler.

Christoph K. Themenstarter:in
821 Beiträge seit 2009
vor 6 Jahren

Wieso liest Du nicht einfach die Doku? Keine Lust? Arbeit auf uns abwälzen?

Habe ich, entschuldige bitte, wenn ich ein für mich neues Thema nicht direkt verstehe 😕

Mein Kernproblem ist, dass ich einen Cache implementieren will, der einmalig ist. Was zwei Stunden vorher während irgendeinem Web-Request in den Cache geladen wurde, soll zwei Stunden später auch noch vorhanden sein.

Ich wüste nicht, wie ich das anders lösen kann außer durch eine Objekt, was genau einmal instanziert wird und dann global verfügbar ist.

16.835 Beiträge seit 2008
vor 6 Jahren

Du willst in 20 Minuten die Dokumentation vollständig gelesen (und verstanden haben)?
Im Leben nicht.

Reden wir von ASP.NET 4 oder ASP.NET Core? Was soll gecached werden?
Core hat einen Cache Service mit an Board.

Christoph K. Themenstarter:in
821 Beiträge seit 2009
vor 6 Jahren

Ich benutze ASP.NET 4.

Mein Cache ist auch komplett fertig entwickelt. Es ist eine Klasse, die bestimmte Daten bei Bedarf aus Dateien lädt und diese nach komplexen Kriterien von 1 Stunde bis zu mehreren Tagen im Cache hält. Das funktioniert auch wunderbar, so lange alle die selbe Instanz verwenden.

T
2.224 Beiträge seit 2008
vor 6 Jahren

@Christoph K.
Ist überflüssige Arbeit.
Nutz ASP .NET Bordmittel.

Zum Cachen kannst du seit .Net 2.0 den HTTP Cache verwenden.
Diesen kannst du auch so umsetzen, dass dein Objekt zeitlich begrenzt gespeichert wird.

Link:
ASP.NET Caching

Weiteres Link zu aktuellen Verfahren bei MVC musst du mal suchen.
Aber selbst dort dürfte es fertige Lösungen geben, die dein caching überflüssig machen.
Hier macht es keinen Sinn das Rad neu zu erfinden.

Nachtrag:
heir ein passender Link:
Extensible Output Caching with ASP.NET 4 (VS 2010 and .NET 4.0 Series)

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

Christoph K. Themenstarter:in
821 Beiträge seit 2009
vor 6 Jahren

Vielen Dank, dass mit dem Cache klingt sehr interessant

Was mich immer noch verwundert:

Ich habe gerade mal nachgesehen und es gibt bei meinem IIS Projekt nur einen Worker-Process. Auch die Kontrolle im Code liefert immer die gleiche ProcessId.

Laut der Dokumentation teilen sich alle Threads eines Processes die gleichen statischen Variablen. Wie kann es demnach sein, dass sich meine Singletons zweimal initialisieren?

16.835 Beiträge seit 2008
vor 6 Jahren

Der Worker Process hat damit null zutun. Bitte dazu die Grundlagen zum IIS anschauen.
Das ist ein ganz anderes Thema. Du vermischt wegen fehlenden Grundlagen leider allgemein sehr viel.

Laut der Dokumentation teilen sich alle Threads eines Processes die gleichen statischen Variablen.

Wo hast Du das gelesen? Oder auch nur überflogen? Diese Aussage ist nämlich falsch.

Ein Prozess kann zwar mehrere Threads haben, ein Thread ist immer an einen Prozess gekoppelt.

Statische Variablen sind aber nicht an einen Thread oder Prozess gekoppelt, sondern an eine AppDomain. Und ein Prozess kann mehrere AppDomains haben (was aber meistens nicht der Fall ist; nur wenn man es aktiv will, zB. Pluginsystem).
Das Thema AppDomain vergiss aber bitte wieder, weil ist zu tief an dieser Stelle.

Allgemein wird das Problem aber nicht an der Tatsache eines Singleton liegen, sondern weil Du irgendwo einen Fehler im Code hast, den wir hier nicht sehen.
Deine Singleton Implementierung an dieser Stelle hat sowieso Mangel: egal welche Parameter man übergibt, es würde immer die erste Erzeugung zurück geben.
So entwickelt man keine Singleton Factory.

D
985 Beiträge seit 2014
vor 6 Jahren

Liegt wohl daran, dass dein Instanz-Erzeugungscode nicht threadsafe hinsichtlich dieses Merkmals (nur eine Instanz) ist.

W
955 Beiträge seit 2010
vor 6 Jahren

... bzw die Abfrage, ob eine Instanz existiert nicht im lock { } liegt.

D
985 Beiträge seit 2014
vor 6 Jahren

Nun ja, ich würde einen Check aussen und innen vornehmen um nicht jeden parallelen Zugriff in eine gezwungene Synchronisierung zu zwingen.


        private static object syncRoot = new Object();

        public static RamboService GetInstance(string pageDbPathForFastData, string pageDbPathForSlowData)
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    instance = instance ?? new RamboService(pageDbPathForFastData, pageDbPathForSlowData);
                }
            }
            return instance;
        }

Das bedeutet jetzt nicht, dass ich diese Art des Singletons gutheißen möchte. Gerade auch im Hinblick auf die übergebenen Argumente. Sehr gruselig.

Christoph K. Themenstarter:in
821 Beiträge seit 2009
vor 6 Jahren

Nun ja, ich würde einen Check aussen und innen vornehmen um nicht jeden parallelen Zugriff in eine gezwungene Synchronisierung zu zwingen.

  
        private static object syncRoot = new Object();  
  
        public static RamboService GetInstance(string pageDbPathForFastData, string pageDbPathForSlowData)  
        {  
            if (instance == null)  
            {  
                lock (syncRoot)  
                {  
                    instance = instance ?? new RamboService(pageDbPathForFastData, pageDbPathForSlowData);  
                }  
            }  
            return instance;  
        }  
  

Das bedeutet jetzt nicht, dass ich diese Art des Singletons gutheißen möchte. Gerade auch im Hinblick auf die übergebenen Argumente. Sehr gruselig.

Was meinst du genau mit außen und innen ?
Bei dem Beispiel war der lock(syncRoot) an der falschen stelle. Ich habe ihn jetzt nach außen gebracht. Aber wozu brauche ich den inneren dann noch?

Das mit den Argumenten ist nicht schön, ich weiß - allerdings sind die Argumente immer die gleichen und hängen nur davon ab, in welchem Projekt ich die Klasse verwende.

D
985 Beiträge seit 2014
vor 6 Jahren

Mit Außen und Innen bezog ich mich auf den lock.

Hier mal zum besseren Verständnis ein leicht abgewandelter Code-Schnipsel:


public static RamboService GetInstance(...)
{

#region Optional

    // Dieser Code ist für die zuverlässige Funktion nicht erforderlich
    // allerdings verhindert er, dass JEDER Aufruf durch den LOCK muss

    // Wenn es eine Instanz gibt
    if ( instance != null )
        // dann können wir diese auch gleich zurückgeben
        return instance;

#endregion

    lock(syncroot)
    {
        // Wenn wir den lock erhalten haben, müssen wir nochmals prüfen
        // ob wir jetzt wirklich keine Instanz haben
        if (instance == null )
            // Erzeugen der Instanz
            instance = new RamboService(...);
        return instance;
    }
}

16.835 Beiträge seit 2008
vor 6 Jahren

Das mit den Argumenten ist nicht schön, ich weiß - allerdings sind die Argumente immer die gleichen und hängen nur davon ab, in welchem Projekt ich die Klasse verwende.

Dann lies aus einer Config statt Parameter zu verwenden.
So ist das strukturell einfach logisch falsch.

Dann kann man auch einfach Lazy<T> verwenden, wenn es überhaupt sein muss.
Ich sehe hier immer noch ganz klar Dependency Injection in der richtigen Rolle statt dieses gebastle.