2012-03-28 10 views
6

Ich habe derzeit ein Singleton, das bis zu 10 Sekunden dauern kann, um zu initialisieren. Ich möchte jedoch nicht, dass meine Benutzer für diese Initialisierung bestraft werden. Daher würde ich diese Komponente während des Startvorgangs lieber in einen Hintergrundthread umwandeln. Hier ist, was ich habe:Gemeinsames Muster zum Initialisieren eines Singleton auf einem Hintergrund Thread

Singleton:

public class MySingleton 
{ 
    private static MySingleton _instance; 
    private static readonly object _locker = new object(); 

    private MySingleton() 
    { 
     Init(); 
    } 
    public static MySingleton Instance 
    { 
     if(_instance == null) _instance = new MySingleton(); 
     return _instance; 
    } 
    public void Init() 
    { 
     lock(_locker) 
     { 
      if(_instance != null) return; 

      // long running code here... 
     } 
    } 
} 

Anwendungsstart:

Task.Factory.StartNew(() => MySingleton.Instance.Init()); 

Dieser Code funktioniert, schützt vor Doppel init, schützt gegen den Rand Fall des Benutzers braucht es, bevor es Initialisierung durchgeführt und wehrt auch gegen jemanden, der vergessen hat, Init() aufzurufen.

Allerdings fühlt es sich aus zwei Gründen ein wenig klobig an: a) Ich gehe beim Start zweimal in die Init-Methode. b) Ich möchte Threading innerhalb des Singleton tun, aber etwas muss die Initialisierung initiieren.

Gibt es eine sauberere/nettere/bessere Methode, damit umzugehen?

Vielen Dank im Voraus für die Hilfe aller.

** BEARBEITEN: Wie in den Kommentaren darauf hingewiesen, wurde Init irrtümlicherweise als privat definiert. Es sollte öffentlich sein und wurde korrigiert.

+1

Sie sollten Ihre Singleton initialisieren [wie predigte von Jon Skeet] (http://csharpindepth.com/Articles/ mit 'Faulen <>' werden General/Singleton.aspx), um die Initialisierung zu entladen, bis das Singleton tatsächlich verwendet wird. –

+1

Jon besagt in diesem Artikel "Meine persönliche Präferenz ist für Lösung 4:", die nicht die Lazy Lösung ist. Bitte lesen Sie den Artikel für seine Argumentation und weitere Informationen. –

+0

@MitchWheat - Heh, ich denke (in den 10+ Zeiten, die ich es gelesen habe) habe ich nie den Artikel nach den Beispielen gelesen. Das wird mir beibringen, meine Zunge zu halten (wahrscheinlich nicht) –

Antwort

0

Sie sollten wie unten Singletonklasse definieren und rufen ...

var instance = MySingleton.Instance; 
    while (true) 
    { 
    /// check for whether singleton initialization complete or not 
    if (MySingleton.Initialized) 
    { 
     break; 
    } 
    } 



    public class MySingleton 
     { 
      private static MySingleton _instance; 
      private static readonly object _locker = new object(); 
      public static bool Initialized { get; set; } 

      private MySingleton() 
      { 
       ThreadPool.QueueUserWorkItem(call => Init()); 
      } 

      public static MySingleton Instance 
      { 
       get 
       { 
        if (_instance == null) 
         _instance = new MySingleton(); 

        return _instance; 
       } 
      } 

      private void Init() 
      { 
       lock (_locker) 
       { 
        if (Initialized) 
         return; 
        // long running code here...   
        for (int i = 0; i < 10000; i++) 
        { 

        } 
        Initialized = true; 
       } 
      } 
     } 
9

Verwenden Sie den statischen Konstruktor es auszulösen und ein ManualResetEvent für die Synchronisierung. Es gibt Ihnen eine Lösung, wo alles innerhalb der eigentlichen Klasse erledigt wird. Es ist daher nicht davon abhängig, dass jemand Ihre init-Methode aufruft.

Das Ereignis bleibt aktiviert, sobald es ausgelöst wird, was bedeutet, dass die Instance-Eigenschaft so schnell wie möglich zurückkehrt, wenn die Init-Methode abgeschlossen ist.

+0

Tolle Lösung! Vielen Dank! Ein paar Fragen, obwohl: a) irgendein Grund zu QueueUserWorkItem und nicht zu Task.Factory.StartNew()? b) Kann man ManualResetEvent und nicht ein AutoResetEvent benutzen? – pdalbe01

+0

a) Nein, persönliche Präferenz. b) Ja. 'AutoResetEvent' setzt das Signal zurück und blockiert alle Anrufer, wenn das erste' Wait' beendet ist. – jgauffin

+1

Geringes Problem: Statische Konstruktoren werden nur bei der ersten Verwendung eines Klassenmembers aufgerufen. Dies würde Init() während des App-Starts nicht ausführen. – pdalbe01

0

Ich würde mybe mit einer gehen Task<T>:

class Program 
{ 
    static void Main(string[] args) 
    { 
     MySingleton.Init(); 

     Thread.Sleep(7000); 

     Console.WriteLine("Getting instance..."); 
     var mySingleton = MySingleton.Instance; 
     Console.WriteLine("Got instance."); 
    } 

    public class MySingleton 
    { 
     private static Lazy<MySingleton> instance; 

     public static MySingleton Instance 
     { 
      get { return instance.Value; } 
     } 

     public static void Init() 
     { 
      var initTask = Task.Factory.StartNew(() => 
      { 
       for(int i = 0; i < 10; i++) 
       { 
        Thread.Sleep(1000); 
        Console.WriteLine("Doint init stuff {0}...", i); 
       } 

       return new MySingleton(); 
      }); 

      instance = new Lazy<MySingleton>(() => initTask.Result); 
     } 

     private MySingleton() { } 
    } 
}