Инициализация поля 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
Тогда вам не нужна перегрузка метода доступа или проверка нуля при каждом чтении.