51

我给自己写了一个多线程随机生成器

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();
    }
}

但是,它在触发 Next() 时会引发 NullReferenceException,我不明白。这种初始化 ThreadStatic 字段是否以某种方式被禁止?

我知道我可以每次都检查该字段是否已初始化,但这不是我正在寻找的解决方案。

4

2 回答 2

79

初始化 ThreadStatic 字段有点棘手。特别是有这个警告:

不要为标有 ThreadStaticAttribute 的字段指定初始值,因为这样的初始化只发生一次,当类构造函数执行时,因此只影响一个线程。

MSDN 文档中。这意味着在初始化类时运行的线程将获得您在字段声明中定义的初始值,但所有其他线程的值都将为 null。我认为这就是为什么您的代码表现出问题中描述的不良行为的原因。

这个博客有更完整的解释。

(来自博客的片段)

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

ThreadStatic 在静态构造函数中初始化——它只执行一次。因此,当静态构造函数执行时,只有第一个线程被分配“foo 字符串”。在所有后续线程中访问时,Foo 保留为未初始化的 null 值。

解决此问题的最佳方法是使用属性来访问 Foo 道具。

[ThreadStatic]
private static string _foo;

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

请注意,静态属性中不需要锁定,因为每个线程都在作用于_foo仅针对该线程的对象。不能与其他线程争用。这个问题涵盖了这一点:ThreadStatic and Synchronization

于 2013-08-06T17:18:34.487 回答
3

关于问题的原因,先前的答案是正确的。

如果您可以使用 .NET 4 或更高版本,请使用 ThreadLocal 代替,因为它是使用初始化程序构建的。

请参阅ThreadStatic vs ThreadLocal<T>:泛型比属性好吗?

然后,您不需要在每次读取时都进行访问器重载或空值检查。

于 2019-06-07T23:43:42.180 回答