Инициализация потоков статических переменных

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

public class MyClass // bad code, do not use
{
    static string _myResource = "";
    static volatile bool _init = false;
    public MyClass()
    {
        if (_init == true) return;
        lock (_myResource)
        {
            if (_init == true) return;
            Thread.Sleep(3000); // some operation that takes a long time 
            _myResource = "Hello World";
            _init = true;
        }
    }
    public string MyResource { get { return _myResource; } }
}

Здесь есть какие-то отверстия? Возможно, есть более простой способ сделать это.

UPDATE: Консенсус, похоже, заключается в том, что статический конструктор - это путь. Я придумал следующую версию, используя статический конструктор.

public class MyClass
{
    static MyClass() // a static constructor
    {
        Thread.Sleep(3000); // some operation that takes a long time 
        _myResource = "Hello World";
    }

    static string _myResource = null;

    public MyClass() { LocalString = "Act locally"; } // an instance constructor

    // use but don't modify
    public bool MyResourceReady { get { return _myResource != null; } }
    public string LocalString { get; set; }
}

Я надеюсь, что это лучше.

Ответы

Ответ 1

Вы можете использовать статические конструкторы для инициализации ваших статических переменных, которые гарантируют С# только один раз в каждом AppDomain. Не уверены, считали ли вы их.

Итак, вы можете прочитать это: http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx (Статические конструкторы)

И это: Безопасен ли поток статического конструктора С#?

Ответ 2

Выполнение lock() on _myResource и изменение его внутри оператора lock() кажется плохой идеей. Рассмотрим следующий рабочий процесс:

  • поток 1 вызывает MyClass().
  • выполнение выполняется перед строкой _init = true; сразу после назначения _myResource.
  • процессор переключается на поток 2.
  • поток 2 вызывает MyClass(). Поскольку _init по-прежнему false и refrence _myResource изменен, он успешно входит в блок lock().
  • _init по-прежнему false, поэтому поток 2 переназначает _myResource.

Обходной путь: создайте статический object и заблокируйте этот объект вместо инициализированного ресурса:

private static readonly object _resourceLock = new object();

/*...*/

lock(_resourceLock)
{
    /*...*/
}

Ответ 3

Ваш класс небезопасен:

  • Вы меняете объект, который вы блокируете, после того, как вы заблокировали его.
  • У вас есть свойство, которое получает ресурс без его блокировки.
  • Вы блокируете примитивный тип, что обычно не является хорошей практикой.

Это должно сделать это за вас:

public class MyClass
{
    static readonly object _sync = new object();
    static string _myResource = "";
    static volatile bool _init = false;

    public MyClass()
    {
        if (_init == true) return;
        lock (_sync)
        {
            if (_init == true) return;
            Thread.Sleep(3000); // some operation that takes a long time 
            _myResource = "Hello World";
            _init = true;
        }
    }

    public string MyResource 
    { 
        get 
        { 
            MyClass ret; // Correct
            lock(_sync)
            {
                ret = _myResource;
            }
            return ret;
        } 
    }
}

Update:
Правильно, статический ресурс не должен возвращаться напрямую... Я скорректировал свой пример соответственно.

Ответ 4

В зависимости от вашего варианта использования (т.е. если потокам не нужно передавать информацию друг другу с помощью этой переменной), обозначение переменной-члена как [ThreadStatic] может быть решением.
См. здесь.

Ответ 5

static string _myResource = "";
...
public MyClass()
{
    ...
    lock (_myResource)
    {
    }
}

Из-за прерывания строки вы не должны блокировать строковый литерал.

ОБНОВЛЕНИЕ

Да, @RaoulRubin, поэтому, если вы заблокируете строковый литерал и этот строковый литерал используется несколькими классами, вы будете использовать эту блокировку. Это может привести к неожиданному поведению.