# 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 как критические и относятся к типу: Ошибки при работе с многопоточными примитивами (интерфейсами запуска потоков на выполнение, синхронизации и обмена данными между потоками и пр.).

Данная диагностика классифицируется как:

CWE-665

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V3089.