Czy wątek C# static constructor jest bezpieczny?

Innymi słowy, czy wątek implementacji Singletona jest Bezpieczny:

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

    static Singleton()
    {
        instance = new Singleton();
    }

    public static Singleton Instance
    {
        get { return instance; }
    }
}
Author: urini, 2008-08-10

10 answers

Konstruktory statyczne mogą być uruchamiane tylko raz na domenę aplikacji, przed utworzeniem jakichkolwiek instancji klasy lub dostępem do statycznych elementów. http://msdn.microsoft.com/en-us/library/aa645612.aspx

Pokazana implementacja jest bezpieczna dla początkowej konstrukcji, tzn. do zbudowania obiektu Singleton nie jest wymagane żadne blokowanie ani testowanie null. Nie oznacza to jednak, że jakiekolwiek użycie instancji zostanie zsynchronizowane. Istnieją różne sposobów, że można to zrobić; pokazałem jeden poniżej.

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();
    }
}
 170
Author: Zooba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-10 08:46:48

Chociaż wszystkie te odpowiedzi dają tę samą ogólną odpowiedź, jest jedno zastrzeżenie.

Pamiętaj, że wszystkie potencjalne pochodne klasy generycznej są kompilowane jako poszczególne typy. Należy więc zachować ostrożność przy implementowaniu statycznych konstruktorów dla typów generycznych.

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

EDIT:

Oto demonstracja:

static void Main(string[] args)
{
    var obj = new Foo<object>();
    var obj2 = new Foo<string>();
}

public class Foo<T>
{
    static Foo()
    {
         System.Diagnostics.Debug.WriteLine(String.Format("Hit {0}", typeof(T).ToString()));        
    }
}

W konsoli:

Hit System.Object
Hit System.String
 76
Author: Brian Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-07 18:11:50

Używanie statycznego konstruktora w rzeczywistości jest threadsafe. Konstruktor statyczny ma gwarancję wykonania tylko raz.

Ze specyfikacji języka C# http://msdn.microsoft.com/en-us/library/aa645612 (VS. 71). aspx :

Statyczny konstruktor klasy wykonuje co najwyżej raz w danej domenie aplikacji. Wykonanie statycznego konstruktora jest wywoływane przez pierwsze z następujących zdarzeń, które mają miejsce w domenie aplikacji:

  • instancja klasy jest tworzona.
  • każdy ze statycznych członków klasy jest odwołany.

Więc tak, możesz ufać, że Twój singleton będzie poprawnie utworzony.

Zooba zrobił świetny punkt (i 15 sekund przede mną, też!), że statyczny konstruktor nie zagwarantuje thread-safe shared access to the singleton. Trzeba będzie to załatwić w inny sposób.

 24
Author: Derek Park,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-10 08:50:42

Oto Wersja Cliffnotes z powyższej strony MSDN w c# singleton:

Użyj następującego wzoru, zawsze, nie możesz się pomylić:

public sealed class Singleton
{
   private static readonly Singleton instance = new Singleton();

   private Singleton(){}

   public static Singleton Instance
   {
      get 
      {
         return instance; 
      }
   }
}

Poza oczywistymi funkcjami Singletona, daje Ci te dwie rzeczy za darmo (w odniesieniu do Singletona w c++):

  1. leniwa konstrukcja (lub żadna konstrukcja, jeśli nigdy nie została nazwana)
  2. synchronizacja
 7
Author: Jay Juch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-23 19:34:05

Konstruktory statyczne są gwarantowane, aby odpalić tylko raz na domenę aplikacji, więc twoje podejście powinno być OK. Jednak funkcjonalnie nie różni się od bardziej zwięzłej wersji inline:

private static readonly Singleton instance = new Singleton();

Bezpieczeństwo wątku jest bardziej problemem, gdy leniwie inicjujesz rzeczy.

 4
Author: Andrew Peters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-10 08:48:33

Specyfikacja Common Language Infrastructure gwarantuje, że " inicjalizacja typu będzie uruchamiana dokładnie raz dla danego typu, chyba że jawnie zostanie wywołana przez kod użytkownika."(Sekcja 9.5.3.1. No chyba, ze masz jakies zwariowane IL na luzie calling Singleton::.cctor bezpośrednio (mało prawdopodobne) twój statyczny konstruktor uruchomi się dokładnie raz przed użyciem typu Singleton, zostanie utworzona tylko jedna instancja Singletona, a Twoja właściwość instancji jest bezpieczna dla wątków.

Zauważ, że jeśli Konstruktor Singletona uzyskuje dostęp do właściwości instancji (nawet pośrednio), wtedy właściwość instancji będzie null. Najlepsze, co możesz zrobić, to wykryć, kiedy tak się stanie i rzucić wyjątek, sprawdzając, czy instancja nie jest null w accessorze właściwości. Po zakończeniu konstruktora statycznego właściwość Instance będzie inna niż null.

Jako odpowiedź Zoomba wskazuje, że będziesz musiał uczynić Singleton bezpiecznym, aby uzyskać dostęp z wielu wątków, lub zaimplementować mechanizm blokujący za pomocą przypadek Singletona.

 3
Author: Dominic Cooney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:09

Żeby być pedantycznym, ale nie ma czegoś takiego jak statyczny konstruktor, a raczej inicjalizatory typu statycznego, Oto Małe demo zależności cyklicznego konstruktora statycznego, które ilustruje ten punkt.

 2
Author: Florian Doyon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-07 18:56:01

Konstruktor statyczny ma gwarancję bezpieczeństwa wątku. Sprawdź również dyskusję na temat Singleton w DeveloperZen: http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/

 1
Author: Eran Kampf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-10 23:38:26

Statyczny konstruktor zakończyuruchamianie przed każdy wątek będzie mógł uzyskać dostęp do klasy.

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

Powyższy kod dał wyniki poniżej.

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).

Mimo że statyczny konstruktor długo się uruchamiał, pozostałe wątki zatrzymywały się i czekały. Wszystkie wątki odczytują wartość _x ustawioną na dole statycznego konstruktora.

 1
Author: Trade-Ideas Philip,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-01 06:37:26

Chociaż inne odpowiedzi są w większości poprawne, istnieje jeszcze jedno zastrzeżenie dotyczące statycznych konstruktorów.

Zgodnie z sekcją II.10.5.3.3 rasy i impas z ECMA-335 Common Language Infrastruktura

Sama Inicjalizacja typu nie tworzy impasu, chyba że jakiś kod wywołane z inicjalizatora typu (bezpośrednio lub pośrednio) jawnie wywołuje operacje blokujące.

Następujący kod powoduje, że 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();
    }
}
Autorem jest Igor Ostrovsky, zobacz jego post tutaj.
 0
Author: oleksii,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-25 12:19:27