Инициализация поля 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 и Синхронизация

Ответ 2

Предыдущий ответ является правильным относительно причины проблемы.

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

Посмотрите ThreadStatic против ThreadLocal <T>: универсальный лучше, чем атрибут?

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