Безопасен ли поток статического конструктора C #? - PullRequest
234 голосов
/ 10 августа 2008

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

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

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

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

Ответы [ 10 ]

180 голосов
/ 10 августа 2008

Статические конструкторы гарантированно будут запускаться только один раз в домене приложения, до создания каких-либо экземпляров класса или доступа к любым статическим членам. 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();
    }
}
81 голосов
/ 03 декабря 2008

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

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

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
26 голосов
/ 10 августа 2008

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

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

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

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

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

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

7 голосов
/ 23 ноября 2011

Вот версия Cliffnotes со страницы MSDN выше на c # singleton:

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

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

   private Singleton(){}

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

Помимо очевидных синглтон-функций, он предоставляет вам две бесплатные вещи (в отношении синглтона в c ++):

  1. Ленивая конструкция (или не конструкция, если ее никогда не называли)
  2. 1011 * синхронизация *
4 голосов
/ 10 августа 2008

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

private static readonly Singleton instance = new Singleton();

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

3 голосов
/ 01 января 2017

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

    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, установленное в нижней части статического конструктора.

3 голосов
/ 10 августа 2008

Спецификация Common Language Infrastructure гарантирует, что «инициализатор типа должен запускаться ровно один раз для любого данного типа, если это явно не вызвано кодом пользователя». (Раздел 9.5.3.1.) Таким образом, если у вас нет какого-то дурацкого IL на вызове Singleton ::. Cctor напрямую (маловероятно), ваш статический конструктор будет работать ровно один раз перед использованием типа Singleton, будет создан только один экземпляр Singleton и ваше свойство Instance является поточно-ориентированным.

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

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

2 голосов
/ 02 марта 2010

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

1 голос
/ 11 августа 2008

Статический конструктор гарантированно безопасен для потоков. Также ознакомьтесь с обсуждением Singleton на DeveloperZen: http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/

0 голосов
/ 25 марта 2014

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

Согласно разделу II.10.5.3.3 Расы и взаимоблокировки общего языка ECMA-335 инфраструктура

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...