Ist der C # statische Konstruktor Thread sicher?

Mit anderen Worten, ist dieser Singleton-Implementierungsthread sicher:

public class Singleton { private static Singleton instance; private Singleton() { } static Singleton() { instance = new Singleton(); } public static Singleton Instance { get { return instance; } } } 

   

Statische Konstruktoren werden garantiert nur einmal pro Anwendungsdomäne ausgeführt, bevor Instanzen einer class erstellt oder auf statische Member zugegriffen wird. http://msdn.microsoft.com/en-us/library/aa645612.aspx

Die gezeigte Implementierung ist threadsicher für die anfängliche Konstruktion, das heißt, für das Konstruieren des Singleton-Objekts ist kein Sperr- oder Null-Test erforderlich. Dies bedeutet jedoch nicht, dass die Verwendung der Instanz synchronisiert wird. Es gibt eine Vielzahl von Möglichkeiten, dies zu erreichen; Ich habe eine unten gezeigt.

 public class Singleton { private static Singleton instance; // Added a static mutex for synchronising use of instance. private static System.Threading.Mutex mutex; private Singleton() { } static Singleton() { instance = new Singleton(); mutex = new System.Threading.Mutex(); } public static Singleton Acquire() { mutex.WaitOne(); return instance; } // Each call to Acquire() requires a call to Release() public static void Release() { mutex.ReleaseMutex(); } } 

Während alle diese Antworten die gleiche allgemeine Antwort geben, gibt es einen Vorbehalt.

Denken Sie daran, dass alle potenziellen Ableitungen einer generischen class als einzelne Typen kompiliert werden. Seien Sie daher vorsichtig, wenn Sie statische Konstruktoren für generische Typen implementieren.

 class MyObject { static MyObject() { //this code will get executed for each T. } } 

BEARBEITEN:

Hier ist die Demonstration:

 static void Main(string[] args) { var obj = new Foo(); var obj2 = new Foo(); } public class Foo { static Foo() { System.Diagnostics.Debug.WriteLine(String.Format("Hit {0}", typeof(T).ToString())); } } 

In der Konsole:

 Hit System.Object Hit System.String 

Die Verwendung eines statischen Konstruktors ist tatsächlich threadsafe. Der statische Konstruktor wird garantiert nur einmal ausgeführt.

Aus der C # -Sprachspezifikation http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx :

Der statische Konstruktor für eine class wird höchstens einmal in einer bestimmten Anwendungsdomäne ausgeführt. Die Ausführung eines statischen Konstruktors wird ausgetriggers, indem das erste der folgenden Ereignisse in einer Anwendungsdomäne auftritt:

  • Eine Instanz der class wird erstellt.
  • Auf alle statischen Member der class wird verwiesen.

Ja, Sie können darauf vertrauen, dass Ihr Singleton korrekt instanziiert wird.

Zooba hat einen ausgezeichneten Punkt (und 15 Sekunden vor mir!) Gemacht, dass der statische Konstruktor keinen thread-sicheren gemeinsamen Zugriff auf den Singleton garantiert. Das muss auf andere Weise gehandhabt werden.

Hier ist die Cliffnotes-Version von der obigen MSDN-Seite auf c # singleton:

Benutze das folgende Muster, immer kannst du nichts falsch machen:

 public sealed class Singleton { private static readonly Singleton instance = new Singleton(); private Singleton(){} public static Singleton Instance { get { return instance; } } } 

Neben den offensichtlichen Singleton-Features gibt es diese zwei Dinge kostenlos (in Bezug auf Singleton in C ++):

  1. faule Konstruktion (oder keine Konstruktion, wenn sie nie aufgerufen wurde)
  2. Synchronisation

Statische Konstruktoren werden garantiert nur einmal pro App Domain ausgetriggers, daher sollte Ihre Vorgehensweise in Ordnung sein. functionell unterscheidet es sich jedoch nicht von der prägnanten Inline-Version:

 private static readonly Singleton instance = new Singleton(); 

Fadensicherheit ist mehr ein Problem, wenn Sie faul initialisieren.

Die Common Language Infrastructure-Spezifikation garantiert, dass “ein Typinitialisierer für jeden beliebigen Typ genau einmal ausgeführt werden muss, sofern er nicht explizit vom Benutzercode aufgerufen wird.” (Abschnitt 9.5.3.1.) Wenn Sie also nicht irgendeine verrückte IL haben, die Singleton ::. Cctor direkt (unwahrscheinlich) aufruft, wird Ihr statischer Konstruktor genau einmal ausgeführt, bevor der Singleton-Typ verwendet wird. Es wird nur eine Instanz von Singleton erstellt. und Ihre Instance-Eigenschaft ist Thread-sicher.

Beachten Sie, dass, wenn Singletons Konstruktor auf die Instance-Eigenschaft zugreift (auch indirekt), die Instance-Eigenschaft null ist. Das Beste, was Sie tun können, ist zu erkennen, wann dies geschieht, und eine Ausnahme auszulösen, indem Sie prüfen, ob diese Instanz im Eigenschaftenaccessor nicht null ist. Nach Abschluss des statischen Konstruktors ist die Instance-Eigenschaft nicht null.

Wie Zoombas Antwort darauf hinweist, müssen Sie Singleton für den Zugriff aus mehreren Threads sicher machen oder einen Sperrmechanismus für die Verwendung der Singleton-Instanz implementieren.

Nur um pedantisch zu sein, aber es gibt keinen statischen Konstruktor, sondern statische Initialisierer, hier ist eine kleine Demonstration der zyklischen statischen Konstruktorabhängigkeit, die diesen Punkt veranschaulicht.

Statischer Konstruktor ist garantiert threadsicher. Lesen Sie auch die Diskussion zu Singleton auf DeveloperZen: http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/

Der statische Konstruktor wird beendet , bevor ein Thread auf die class zugreifen darf.

  private class InitializerTest { static private int _x; static public string Status() { return "_x = " + _x; } static InitializerTest() { System.Diagnostics.Debug.WriteLine("InitializerTest() starting."); _x = 1; Thread.Sleep(3000); _x = 2; System.Diagnostics.Debug.WriteLine("InitializerTest() finished."); } } private void ClassInitializerInThread() { System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() starting."); string status = InitializerTest.Status(); System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() status = " + status); } private void classInitializerButton_Click(object sender, EventArgs e) { new Thread(ClassInitializerInThread).Start(); new Thread(ClassInitializerInThread).Start(); new Thread(ClassInitializerInThread).Start(); } 

Der obige Code ergab die folgenden Ergebnisse.

 10: ClassInitializerInThread() starting. 11: ClassInitializerInThread() starting. 12: ClassInitializerInThread() starting. InitializerTest() starting. InitializerTest() finished. 11: ClassInitializerInThread() status = _x = 2 The thread 0x2650 has exited with code 0 (0x0). 10: ClassInitializerInThread() status = _x = 2 The thread 0x1f50 has exited with code 0 (0x0). 12: ClassInitializerInThread() status = _x = 2 The thread 0x73c has exited with code 0 (0x0). 

Obwohl der statische Konstruktor sehr lange brauchte, stoppten die anderen Threads und warteten. Alle Threads lesen den Wert von _x am unteren Rand des statischen Konstruktors.

Obwohl andere Antworten größtenteils korrekt sind, gibt es noch einen weiteren Vorbehalt mit statischen Konstruktoren.

Siehe Abschnitt II.10.5.3.3 Races und Deadlocks der ECMA-335 Common Language Infrastructure

Die Typinitialisierung allein darf keinen Deadlock verursachen, es sei denn, ein von einem Typinitialisierer (direkt oder indirekt) aufgerufener Code ruft explizit Blockierungsoperationen auf.

Der folgende Code führt zu einem Deadlock

 using System.Threading; class MyClass { static void Main() { /* Won't run... the static constructor deadlocks */ } static MyClass() { Thread thread = new Thread(arg => { }); thread.Start(); thread.Join(); } } 

Originalautor ist Igor Ostrovsky, siehe seinen Beitrag hier .