V3089. Initializer of a field marked by [ThreadStatic] attribute will be called once on the first accessing thread. The field will have default value on different threads.
Анализатор обнаружил подозрительный фрагмент кода, в котором поле, отмеченное атрибутом [ThreadStatic] инициализируется при объявлении или в статическом конструкторе.
Если выполняется инициализация при объявлении, поле будет проинициализировано этим значением только у первого обратившегося потока. В остальных потоках это поле будет содержать значение, предусмотренное по умолчанию.
Схожая ситуация со статическим конструктором – он выполняется только один раз, и поле будет проинициализировано только в потоке, в котором статический конструктор выполнится.
Рассмотрим пример ситуации с инициализацией при объявлении:
class SomeClass
{
[ThreadStatic]
public static Int32 field = 42;
}
class EntryPoint
{
static void Main(string[] args)
{
new Task(() => { var a = SomeClass.field; }).Start(); // a == 42
new Task(() => { var a = SomeClass.field; }).Start(); // a == 0
new Task(() => { var a = SomeClass.field; }).Start(); // a == 0
}
}
При обращении первого потока к полю 'field' оно будет проинициализировано заданным программистом значением. Таким образом, переменная 'a' будет иметь значение '42', равно как и поле 'field'.
При запуске последующих потоков и обращении к полю, оно уже будет проинициализировано значением по умолчанию ('0' в данном случае), поэтому во всех последующих потоков значение 'a' будет равно '0'.
Как упоминалось ранее, статический конструктор не является решением проблемы – он будет вызван 1 раз, при инициализации типа, поэтому проблема остаётся актуальной.
Решением может послужить использование свойства в качестве обёртки над полем, куда можно дописать дополнительную логику по инициализации поля. Это решает проблему, но опять же, не полностью – при обращении к полю, а не свойству (например, внутри класса), остаётся вероятность получить некорректное значение.
class SomeClass
{
[ThreadStatic]
private static Int32 field = 42;
public static Int32 Prop
{
get
{
if (field == default(Int32))
field = 42;
return field;
}
set
{
field = value;
}
}
}
class EntryPoint
{
static void Main(string[] args)
{
new Task(() => { var a = SomeClass.Prop; }).Start(); // a == 42
new Task(() => { var a = SomeClass.Prop; }).Start(); // a == 42
new Task(() => { var a = SomeClass.Prop; }).Start(); // a == 42
}
}
Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки при работе с многопоточными примитивами (интерфейсами запуска потоков на выполнение, синхронизации и обмена данными между потоками и пр.). |
Данная диагностика классифицируется как:
Взгляните на примеры ошибок, обнаруженных с помощью диагностики V3089. |