2012-04-30 8 views
3

私はこれを数時間熟考してきましたが、私はそれがいかに難しいか少し驚いています。私は、複数のスレッドからアクセスされる基本クラスから、指定されたオブジェクトの反射データの静的キャッシュを初期化しようとしています。私は、キャッシュを初期化するための正しいパターンを考え出すのに苦労しています。スレッドセーフなスタティック・キャッシュのリフレクション・データの初期化方法

最初の考えは、静的キャッシュをnullに初期化し、コンストラクタでnullであるかどうかを確認し、そうでない場合はビルドして設定することでした。すなわち:上書き私は最初のものは、まだキャッシュに移入されている間、別のオブジェクトを構築する場合、これはその中に欠陥があり

class TestBase 
{ 
    private static ConcurrentDictionary<string, PropertyInfo> Cache; 

    protected TestBase() 
    { 
    if(Cache == null) 
    { 
     ConcurrentDictionary<string, PropertyInfo> cache = 
     new ConcurrentDictionary<string, PropertyInfo>(); 
     // Populate... 
     Cache = cache; 
    } 
    } 
} 

は、私は(常にではないが、おそらく)2つのキャッシュと第二の意志を構築してしまいます最初。これはおそらく両方とも完全なキャッシュになるので大丈夫ですが、ハッキーに見えますが、私たちがもっとうまくいくことを願っています。

私の2番目の考えは、インスタンスが作成される前にAppDomainごとに1回だけ呼び出される静的コンストラクタでキャッシュを初期化することでした。 StackOverflowには、この方向を指す同様の質問に対するいくつかの回答があるようです。これは、静的コンストラクタが派生型のリフレクションデータにアクセスできないことに気づくまでは素晴らしいようでした。

コンストラクタ内のアクセスを常に同期させることで、キャッシュを作成したり、他のアクセスをブロックしたりするのを防ぐことができますが、ブロックする必要がありますが、すべての構造をロックするだけです一度起こる。私はそのパフォーマンスの影響が気に入らない。

今私が持っているのは、Interlocked.ExchangeとManualResetEventSlimを使って設定されるフラグです。それは次のようになります。

class TestBase 
{ 
    private static ConcurrentDictionary<string, PropertyInfo> Cache; 
    private static volatile int BuildingCache = 0; 
    private static ManualResetEventSlim CacheBuilt = 
    new ManualResetEventSlim(); 

    protected TestBase() 
    { 
    if(Interlocked.Exchange(ref BuildingCache, 1) == 0) 
    { 
     Cache = new ConcurrentDictionary<string, PropertyInfo>(); 
     // Populate... 
     CacheBuilt.Set(); 
    } 
    CacheBuilt.Wait(); 
    } 
} 

私はそこにすでに受け入れられ、あるいは少なくとも、この種のものを行う方法を知られているかもしれないと思われるが - これはそれですか?そうでない場合は、キャッシュの初期化を同期するためのより良い方法はありますか?問題は、キャッシュアクセスをスレッドセーフにする方法ではなく、ConcurrentDictionary(またはこれに類する)を使用することによって想定できることです。

+0

I _think_ even dictionaryは、読み取り専用の 'get'のスレッドセーフです。だからあなたのキャッシュが準備ができたら、私はConcurrentDictionaryが必要だとは思わない。あなたが興味を持っているかどうかテストする/調べる価値がある – payo

+0

静的なコンストラクタが静的なコンストラクタにアクセスすることができなくなるまで、静的なコンストラクタがアクセスできないことを理解するまで、派生型の反映データ。 " – payo

+0

@payo - 派生型のTypeインスタンスへの参照はどのように取得されましたか? 'this.GetType()'は静的コンストラクタで利用できません( "this"はありません)。おそらく、私は十分に明確ではありませんでした - 私は派生型(および複数の型があります)を知らない。つまり、基本クラスの静的コンストラクタに 'Type type = typeof(SomeDerivedType);'を実行してキャッシュを構築することはできません。 – daveaglick

答えて

3

クラスLazy<T>を使用すると、スレッドセーフな方法で、独自の配管を作成することなく、何かを遅らせることができます。

反射データを熱心にキャッシュしたい場合は、互換性のあるタイプ(特定の属性で装飾されているタイプなど)をスキャンするにはAssembly.GetTypes()を使用する必要があります。例えば、

var types = typeof(TestBase).Assembly.GetTypes().Where(type => --some condition--); 
+0

それは優れています - 前にそのクラスに出くわしたことはありませんでした。 Lazy 値が初期化されている間に値のブロック(私はそれが必要と仮定します)?もしそうなら、これは完璧に見えます。 – daveaglick

+1

@somedaveデフォルトでブロックします(ただし、この動作を変更することはできます)。最初のスレッドは、他のすべてのスレッドのすべてを初期化します。これと他の遅延初期化の概念の詳細については、[Lazy Initialization on MSDN](http://msdn.microsoft.com/en-us/library/dd997286.aspx)を参照してください。 –

+0

@somedave:初期化が完了するまで常にブロックされます。 ['LazyThreadSafetyMode'](http://msdn.microsoft.com/en-us/library/system.threading.lazythreadsafetymode.aspx)を受け入れるコンストラクタオーバーロードを使用して、初期化ルーチンを実行するスレッドを構成することができます。列挙。 –

0

コンストラクタ(private static ConcurrentDictionary<string, PropertyInfo> Cache = new ConcurrentDictionary<string, PropertyInfo>();)の外にディクショナリを作成し、キャッシュを構築する必要があるかどうかを判断するためにCountをチェックする必要があります。次に、キャッシュを構築するときに追加操作を実行するのにTryAddを使用する場合は、スレッドロックをまったく行う必要はありません。

あなたがコメント内で出した問題は、別のプロパティIsCahceValidを使って処理して、キャッシュが完了したことを示すことができます。 TryAddを使用しているので、変数をロックする必要はありません。これは、最初の補完が正常に機能した時点でtrueに設定してからです。

関連する問題