Анализатор обнаружил подозрительный фрагмент кода, в котором поле, отмеченное атрибутом [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 } }
Данная диагностика классифицируется как:
Взгляните на примеры ошибок, обнаруженных с помощью диагностики V3089. |