Я использовал этот шаблон для инициализации статических данных в моих классах. Это выглядит ориентированным на многопотоковое исполнение мне, но я знаю, как тонкие проблемы многопоточности могут быть. Вот код:
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; } }
}
Есть ли здесь какие-либо дыры? Возможно, существует более простой способ сделать это.
ОБНОВЛЕНИЕ: Согласие, кажется, что статический конструктор является способом пойти. Я придумал следующую версию с помощью статического конструктора.
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; }
}
Я надеюсь, что это лучше.
Вы можете использовать статические конструкторы для инициализации статических переменных, которые, как гарантирует C#, будут вызваны только один раз в пределах каждого AppDomain. Не уверен, что вы их рассматривали.
Итак, вы можете прочитать следующее: http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx (Static Constructors)
And this: Является ли статический конструктор C# потокобезопасным?
Выполнение lock()
на _myResource
и изменение его внутри оператора lock()
кажется плохой идеей.
Рассмотрим следующий рабочий процесс:
MyClass()
. _init = true;
сразу после присвоения _myResource
. MyClass()
. Поскольку _init
все еще false
и ссылка _myResource
изменилась, он успешно входит в блок оператора lock()
. _init
по-прежнему false
, поэтому поток 2 переназначает _myResource
. Обходной путь: создать статический объект
и заблокировать этот объект вместо инициализированного ресурса:
private static readonly object _resourceLock = new object();
/*...*/
lock(_resourceLock)
{
/*...*/
}
Ваш класс небезопасен:
Это должно сделать это за вас:
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;
}
}
}
Обновление:
Верно, статический ресурс не должен возвращаться напрямую ... Я соответствующим образом исправил свой пример.
В зависимости от вашего случая использования (т.е. если потокам не нужно передавать информацию друг другу с помощью этой переменной),пометка переменной-члена как [ThreadStatic]
может быть решением.
См. здесь.
static string _myResource = "";
...
public MyClass()
{
...
lock (_myResource)
{
}
}
Из-за интернирования строки вам не следует блокировать строковый литерал. Если вы заблокируете строковый литерал, и этот строковый литерал используется несколькими классами, вы можете использовать эту блокировку совместно. Это потенциально может вызвать неожиданное поведение.