У меня есть статический класс С#, доступ к которому из нескольких потоков. Два вопроса:
Использование статического класса из разных потоков:
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 100; i++)
{
Task.Run(() =>
{
string name = MyStaticClass.GetValue(9555);
//...
});
}
}
}
Вариант 1 статического класса:
public static class MyStaticClass
{
private static MyClass _myClass = new MyClass();
public static string GetValue(int key)
{
return _myClass.GetValue(key);
}
}
Вариант 2 статического класса:
public static class MyStaticClass
{
private static MyClass _myClass;
private static object _lockObj = new object();
static MyStaticClass()
{
InitMyClass();
}
private static void InitMyClass()
{
if (_myClass == null)
{
lock(_lockObj)
{
if (_myClass == null)
{
_myClass = new MyClass();
}
}
}
}
public static string GetValue(int key)
{
return _myClass.GetValue(key);
}
}
Класс экземпляра, созданный из статического класса:
public class MyClass
{
private Dictionary<int, Guid> _valuesDict = new Dictionary<int, Guid>();
public MyClass()
{
for (int i = 0; i < 10000; i++)
{
_valuesDict.Add(i, Guid.NewGuid());
}
}
public string GetValue(int key)
{
if (_valuesDict.TryGetValue(key, out Guid value))
{
return value.ToString();
}
return string.Empty;
}
}
Должен ли я блокировать при инициализации частных статических полей из статического конструктора?
Пусть не хоронят здесь Леду
Никогда не блокируйте в статическом конструкторе. Статические конструкторы уже заблокированы платформой, поэтому они запускаются в одном потоке ровно один раз.
Это особый случай более общего совета: никогда не делайте ничего особенного с потоками в статическом конструкторе. Тот факт, что статические конструкторы эффективно заблокированы и эта блокировка может быть оспорена любым кодом, который обращается к вашему типу, означает, что вы можете очень быстро попасть в тупики, которые вы не ожидали и которые трудно увидеть. Я привожу пример здесь: https://ericlippert.com/2013/01/31/the-no-lock-deadlock/
Если вы хотите ленивой инициализации, используйте конструкцию Lazy<T>
; это было написано экспертами, которые знают, как сделать это безопасно.
Являются ли мои частные статические поля безопасными для потоков, когда поле инициализируется при объявлении?
Потоковая безопасность - это сохранение программных инвариантов, когда программные элементы вызываются из нескольких потоков. Вы не сказали, каковы ваши инварианты, поэтому невозможно сказать, является ли ваша программа "безопасной".
Если инвариант, о котором вы беспокоитесь, заключается в том, что статический конструктор работает до того, как будет выполнен первый статический метод или создан первый экземпляр типа, С# гарантирует это. Конечно, если вы пишете сумасшедший код в своем статическом конструкторе, тогда могут произойти сумасшедшие вещи, поэтому, опять же, постарайтесь сделать ваши статические конструкторы очень простыми.
поля статического класса по умолчанию не являются потокобезопасными, и их следует избегать, если только они не предназначены для чтения. Здесь обратная сторона также является "блокировкой", она создаст сериализованную обработку в многопоточной среде.
public static class MyStaticClass
{
private static MyClass _myClass;
private static object _lockObj;
static MyStaticClass()
{
_myClass = new MyClass();
_lockObj = new object();
}
public static string GetValue(int key)
{
return _myClass.GetValue(key);
}
public static void SetValue(int key)
{
lock(_lockObj)
{
_myClass.SetValue(key);
}
}
}
Ваша вторая версия предпочтительнее. Вы можете заблокировать его немного больше, сделав поле readonly
:
public static class MyStaticClass
{
private static readonly MyClass _myClass = new MyClass();
public static string GetValue(int key)
{
return _myClass.GetValue(key);
}
}
Ваше намерение заключается в том, что _myClass
изначально установлен на экземпляр MyClass
и никогда не устанавливается на другой. readonly
выполняет это, указывая, что он может быть установлен только один раз, либо в статическом конструкторе, либо инициализируя его, как указано выше. Мало того, что другой поток не может установить его, но любая попытка изменить его приведет к ошибке компилятора.
Вы можете опустить readonly
и просто никогда не устанавливать _myClass
снова, но readonly
одновременно сообщает и реализует ваши намерения.
Вот где это становится сложнее: Ваша ссылка на экземпляр MyClass
является поточно-ориентированной. Вам не нужно беспокоиться о том, будут ли различные потоки заменять его другим экземпляром (или устанавливать его на ноль), и он будет создан до того, как какой-либо поток попытается взаимодействовать с ним.
Что это не делает, так это делает поток MyClass
безопасным. Не зная, что он делает или как вы с ним взаимодействуете, я никак не могу сказать, каковы потребности или проблемы.
Если это проблема, один из подходов заключается в использовании lock
для предотвращения одновременного доступа, который не должен происходить, в точности как продемонстрировал @Mahi1722. Я включил код из этого ответа (не для плагиата, но если что-то случится с этим ответом, то этот будет ссылаться на ответ, который не существует.)
public static class MyStaticClass
{
private static MyClass _myClass = new MyClass();
private static object _lockObj = new object();
public static string GetValue(int key)
{
return _myClass.GetValue(key);
}
public static void SetValue(int key)
{
lock(_lockObj)
{
_myClass.SetValue(key);
}
}
}
Оба метода, которые взаимодействуют с _myClass
блокируются с помощью _lockObject
что означает, что любое выполнение любого из них будет блокироваться, пока другой поток выполняет другое.
Это правильный подход. Другой - сделать поток MyClass
безопасным, либо используя параллельные коллекции, либо реализуя такие блокировки в этом классе. Таким образом, вам не нужно использовать операторы lock
в каждом классе, который использует экземпляр MyClass
. Вы можете просто использовать его, зная, что он управляет этим внутри.
Оба являются правильными, но нет необходимости lock
внутри static constructor
. Итак, я выберу первый вариант, он короче и понятнее
Статический конструктор для класса выполняется не более одного раза в данном домене приложения. Выполнение статического конструктора инициируется первым из следующих событий, происходящих в домене приложения:
Экземпляр класса создан.
Все статические члены класса имеют ссылки.
Таким образом, оба ваших варианта работают, но вам не нужно блокировать конструктор, как во втором варианте.
Однако в целом: если у вас есть доступ на запись к статической переменной вне статического конструктора, каждый доступ на чтение и запись к этой переменной должен быть заблокирован.