Инициализация поля ThreadStatic по-прежнему вызывает исключение NullReferenceException

Я написал себе многопоточный генератор случайных чисел

public static class MyRandGen
{
    private static Random GlobalRandom = new Random();
    [ThreadStatic]
    private static Random ThreadRandom = new Random(SeedInitializer());
    private static int SeedInitializer()
    {
        lock (GlobalRandom) return GlobalRandom.Next();
    }

    public static int Next()
    {
        return ThreadRandom.Next();
    }
}

Тем не менее, он генерирует исключение NullReferenceException при запуске Next(), что я не понимаю. Такого рода инициализация полей ThreadStatic запрещена?

Я знаю, что могу просто проверить, инициализируется ли поле каждый раз, но это не то решение, которое я ищу.

1 ответ

Решение

Инициализация полей ThreadStatic немного сложнее. В частности, есть такая оговорка:

Не указывайте начальные значения для полей, помеченных ThreadStaticAttribute, потому что такая инициализация происходит только один раз, когда выполняется конструктор класса, и, следовательно, влияет только на один поток.

в документах MSDN. Это означает, что поток, выполняющийся при инициализации класса, получает то начальное значение, которое вы определили в объявлении поля, но все остальные потоки будут иметь значение null. Я думаю, именно поэтому ваш код демонстрирует нежелательное поведение, описанное в вашем вопросе.

Более полное объяснение в этом блоге.

(фрагмент из блога)

[ThreadStatic]
private static string Foo = "the foo string";

ThreadStatic инициализируется в статическом конструкторе, который выполняется только один раз. Таким образом, только самому первому потоку назначается "строка foo" при выполнении статического конструктора. При обращении ко всем последующим потокам, Foo остается с неинициализированным нулевым значением.

Лучший способ обойти это - использовать свойство для доступа к Foo.

[ThreadStatic]
private static string _foo;

public static string Foo {
   get {
     if (_foo == null) {
         _foo = "the foo string";
     }
     return _foo;
   }
}

Обратите внимание, что нет необходимости в блокировке статического свойства, потому что каждый поток действует на _foo это только для этой темы. Там не может быть спор с другими потоками. Это покрыто этим вопросом: ThreadStatic и Синхронизация

Предыдущий ответ верен в отношении причины проблемы.

если вы можете использовать.NET 4 или выше, используйте вместо него ThreadLocal, созданный с помощью инициализатора.

См. ThreadStatic против ThreadLocal: общий лучше, чем атрибут?

Тогда вам не нужна перегрузка метода доступа или проверка нуля при каждом чтении.

Другие вопросы по тегам