web-dev-qa-db-ja.com

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

ただし、Next()を起動すると、NullReferenceExceptionがスローされます。これは理解できません。そのようなThreadStaticフィールドの初期化はどういうわけか禁止されていますか?

フィールドが毎回初期化されているかどうかを確認できることはわかっていますが、それは私が探している解決策ではありません。

40
Tarec

ThreadStaticフィールドの初期化は少し注意が必要です。特に、この警告があります。

ThreadStaticAttributeでマークされたフィールドの初期値は指定しないでください。このような初期化は、クラスコンストラクターの実行時に1回だけ発生するため、1つのスレッドにのみ影響します。

MSDNドキュメント で。これが意味するのは、クラスが初期化されるときに実行されるスレッドは、フィールド宣言で定義した初期値を取得しますが、他のすべてのスレッドの値はnullになります。これが、あなたのコードがあなたの質問で説明されている望ましくない振る舞いを示している理由だと思います。

より完全な説明は このブログ にあります。

(ブログからの抜粋)

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

ThreadStaticは、静的コンストラクターで初期化されます。これは1回だけ実行されます。したがって、静的コンストラクターの実行時に、最初のスレッドのみに「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とSynchronization

問題の理由については、以前の回答が正しいです。

if。NET 4以降を使用できる場合は、初期化子で構築されているように、代わりにThreadLocalを使用してください。

ThreadStatic v.s. ThreadLocal <T>:属性よりもジェネリックの方が優れていますか? を参照してください。

そうすれば、読み取りのたびにアクセサーのオーバーロードやnullチェックを行う必要がなくなります。

2
David Burg