Является ли поток статического конструктора С# безопасным?

Другими словами, безопасен ли этот поток реализации Singleton:

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

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

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

Ответ 1

Статические конструкторы гарантированно запускаются только один раз для домена приложения до того, как будут созданы какие-либо экземпляры класса или будут доступны какие-либо статические члены. http://msdn.microsoft.com/en-us/library/aa645612.aspx

Представленная реализация является потокобезопасной для первоначальной конструкции, то есть для построения объекта Singleton не требуется никаких проверок или нулевого тестирования. Однако это не означает, что любое использование экземпляра будет синхронизировано. Существует множество способов, которыми это можно сделать; Я показал один ниже.

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

Ответ 2

Хотя все эти ответы дают один и тот же общий ответ, есть одно предостережение.

Помните, что все потенциальные дифференцирования общего класса скомпилированы как отдельные типы. Поэтому будьте осторожны при реализации статических конструкторов для общих типов.

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

EDIT:

Вот демонстрация:

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

В консоли:

Hit System.Object
Hit System.String

Ответ 3

Использование статического конструктора фактически является потокобезопасным. Статический конструктор гарантированно выполняется только один раз.

Из спецификации языка С# http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx:

Статический конструктор для класса выполняется не более одного раза в заданном домене приложения. Выполнение статического конструктора запускается первым из следующих событий в домене приложения:

  • Создается экземпляр класса.
  • Ссылка на любой из статических членов класса.

Итак, вы можете доверять, что ваш синглтон будет правильно создан.

Zooba сделала отличную точку (и за 15 секунд до меня тоже!), что статический конструктор не гарантирует поточный общий доступ к singleton. Это нужно будет обрабатывать другим способом.

Ответ 4

Здесь версия Cliffnotes с указанной выше страницы MSDN на С# singleton:

Используйте следующий шаблон, всегда, вы не ошибетесь:

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

   private Singleton(){}

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

Помимо очевидных одноэлементных функций, это дает вам эти две вещи бесплатно (в отношении singleton в С++):

  • ленивая конструкция (или нет конструкции, если она никогда не называлась)
  • синхронизация

Ответ 5

Статические конструкторы гарантированно срабатывают только один раз для домена приложения, поэтому ваш подход должен быть в порядке. Тем не менее, это функционально не отличается от более сжатой, встроенной версии:

private static readonly Singleton instance = new Singleton();

Безопасность потоков - это большая проблема, когда вы лениво инициализируете вещи.

Ответ 6

Спецификация общей языковой инфраструктуры гарантирует, что "инициализатор типа должен запускаться ровно один раз для любого заданного типа, если явно не вызывается пользовательским кодом." (Раздел 9.5.3.1.) Итак, если у вас нет какого-нибудь чудаковатого ИЛ на свободном вызове Singleton::. Cctor напрямую (маловероятно), ваш статический конструктор будет работать ровно один раз до использования типа Singleton, будет создан только один экземпляр Singleton, и ваше свойство экземпляра является потокобезопасным.

Обратите внимание, что если конструктор Singleton обращается к свойству Instance (даже косвенно), свойство Instance будет равно null. Лучшее, что вы можете сделать, это обнаружить, когда это произойдет, и выбросить исключение, проверив, что экземпляр не имеет значения null в аксессуре свойств. После завершения вашего статического конструктора свойство Instance будет не нулевым.

Как ответ Zoomba указывает, что вам нужно будет сделать Singleton безопасным для доступа из нескольких потоков или реализовать механизм блокировки вокруг экземпляра singleton.

Ответ 7

Просто, чтобы быть педантичным, но нет такой вещи, как статический конструктор, но скорее статичные инициализаторы типа здесь небольшая демонстрация циклическая статическая зависимость конструктора, которая иллюстрирует эту точку.

Ответ 9

Статический конструктор завершает до. Любой поток может получить доступ к классу.

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

Приведенный выше код привел результаты ниже.

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

Несмотря на то, что статический конструктор занял много времени, остальные потоки остановились и ждали. Все потоки читают значение _x, установленное в нижней части статического конструктора.

Ответ 10

Хотя другие ответы в основном правильны, существует еще одна оговорка со статическими конструкторами.

В соответствии с разделом II.10.5.3.3 Гонки и тупики ECMA-335 Common Language инфраструктура

Только инициализация типа не должна создавать тупик, если только какой-либо код вызывается из инициализатора типа (прямо или косвенно) явно вызывает операции блокировки.

Следующий код приводит к тупиковой ошибке

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

Оригинальный автор - Игорь Островский, см. его пост здесь.