【问题标题】:Initializing ThreadStatic field still causes NullReferenceException初始化 ThreadStatic 字段仍然会导致 NullReferenceException
【发布时间】:2013-08-07 19:36:46
【问题描述】:

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

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 字段是否以某种方式被禁止?

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

【问题讨论】:

  • 您的代码对我来说无一例外。 VS2010\4.0
  • 为什么不使用线程安全的Rngcryptoserviceprovider
  • 我正在寻找一种比简单锁定更快的方法 - Rngcryptoserciveprovider 比这慢几倍。我将了解 Lazy,以为我仍然不知道为什么我的方法不起作用。

标签: c# random thread-safety threadstatic


【解决方案1】:

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

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

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

更完整的解释在this blog。

(来自博客的 sn-p)

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

ThreadStatic 在静态构造函数中初始化 - 仅 执行一次。所以只有第一个线程被分配“foo string" 静态构造函数执行时。在所有访问时 后续线程中,Foo 保留为未初始化的 null 值。

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

[ThreadStatic]
private static string _foo;

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

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

【讨论】:

  • 这就是我一直在寻找的答案。我想我可以避免空值检查。感谢您的帮助。
  • 那个 getter 线程安全吗?如果您有两个方法对同一个线程具有亲和力,并且它们都同时调用了 getter,那么这不会导致潜在的问题。不应该有锁吗?
  • @cost - 每个线程都有自己的_foo,同一线程不能同时访问 getter 多次。见stackoverflow.com/questions/1087599/…。 MSFT 还说 Any public static members of this type are thread safe msdn.microsoft.com/en-us/library/…
  • @cost - 当然,两个不同的线程可以同时命中 getter。但是每个线程都在访问它自己的_foo,该_foo 仅存在于该线程(这就是ThreadStatic 所做的),因此无需锁定以防止它们踩到_foo。他们没有访问相同的_foo。 stackoverflow.com/questions/11507881/… 。至于来自同一个线程的两个不同的程序流……我不知道同一个线程内多个程序流之间的上下文切换的特性。
  • @cost - 线程不能被自己中断。只要 GUI 线程在 getter 中,GUI 线程中的一个 windows 计时器就会被阻塞。我知道单个线程可以在退出第一次之前第二次进入 getter 的唯一方法是,如果您有意通过在 getter 中放置 DoEvents 调用或将 getter 编写为递归来这样做。即使在这些情况下,锁也无济于事。锁只阻塞其他线程。单个线程可以在同一个对象上获得多个锁而不会阻塞自己。这允许它们在可重入代码中使用。
【解决方案2】:

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

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

见ThreadStatic v.s. ThreadLocal<T>: is generic better than attribute?

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

【讨论】:

    猜你喜欢
    • 2022-11-29
    • 2019-07-27
    • 2015-11-27
    • 1970-01-01
    • 2013-07-26
    • 2022-11-21
    • 2011-09-15
    • 1970-01-01
    • 2021-10-08
    相关资源
    最近更新 更多