10

値のキャッシュに使用する場合がいくつかありConcurrentDictionary<TKey, TValue>ますが、多くの場合、を使用して値をキャッシュに追加するかどうかを決定するために、値の検証を実行する必要がありますConcurrentDictionary<TKey, TValue>.GetOrAdd(TKey, Func<TKey, TValue>)

通常、次の線に沿って:

private readonly ConcurrentDictionary<Type, ISomeObject> someObjectCache = 
    new ConcurrentDictionary<Type, ISomeObject>();
public ISomeObject CreateSomeObject(Type someType)
{
    return someObjectCache.GetOrAdd(someType, type =>
    {
        if(!Attribute.IsDefined(someType, typeof(SomeAttribute))
            // Do something here to avoid the instance from being added to
            //    `someObjectCache`

        ISomeObject someObject;
        // Typical factory functionality goes here
        return someObject;
    });
}

今日私がこれを処理する方法は、正常に機能しているように見える例外をスローすることですが、よりクリーンなアプローチ(おそらく、設定できるフラグまたは戻り値を設定できる特定の値)を使用してGetOrAdd、ラムダ(実際には本格的な方法に置き換えることができますが)。

他のLINQのようなメソッドでの私の経験に基づくと、返すnullと、そのようにチェックされずに値が追加されることになります(そして、ILを読み取るとGetOrAdd、同じ問題が発生するように見えます)。動作します。

例外を使用して追加をキャンセルすることを回避できる方法はありGetOrAddますか?

4

2 に答える 2

11

私が読んだことから、 Add ファクトリ メソッドが Get for the same key のすべての呼び出し元の間で 1 回だけ呼び出されるという保証はありません

そのページの関連部分は下部にあり、ここに引用されています。

また、ConcurrentDictionary(Of TKey, TValue) のすべてのメソッドはスレッド セーフですが、すべてのメソッド、特に GetOrAdd と AddOrUpdate がアトミックというわけではありません。これらのメソッドに渡されるユーザー デリゲートは、ディクショナリの内部ロックの外で呼び出されます。(これは、未知のコードがすべてのスレッドをブロックするのを防ぐために行われます。) したがって、次の一連のイベントが発生する可能性があります。

1) threadA は GetOrAdd を呼び出しますが、項目が見つからず、valueFactory デリゲートを呼び出して Add に新しい項目を作成します。

2) スレッド B は同時に GetOrAdd を呼び出し、その valueFactory デリゲートが呼び出され、スレッド A の前に内部ロックに到達するため、その新しいキーと値のペアがディクショナリに追加されます。

3) threadA のユーザー デリゲートが完了し、スレッドがロックに到達しますが、アイテムが既に存在することがわかります。

4) threadA は「Get」を実行し、以前に threadB によって追加されたデータを返します。

したがって、GetOrAdd によって返されるデータが、スレッドの valueFactory によって作成されたデータと同じであるという保証はありません。AddOrUpdate が呼び出されると、同様の一連のイベントが発生する可能性があります。

私がこれを読んだ方法は、Add デリゲートでロックを呼び出している場合でも、add から返された値が実際に使用されるものであるとは限らないということです。

したがって、これ以上ロックを追加する必要はなく、おそらく次のパターンを使用できます。

private ConcurrentDictionary<Type, ISomeObject> someObjectCache = 
    new ConcurrentDictionary<Type, ISomeObject>();
public ISomeObject CreateSomeObject(Type someType)
{

    ISomeObject someObject; 
    if (someObjectCache.TryGet(someType, out someObject))
    {
       return someObject;
    }

    if (Attribute.IsDefined(someType, typeof(SomeAttribute)) 
    { 
        // init someObject here
        someObject = new SomeObject(); 

        return someObjectCache.GetOrAdd(someType, someObject); // If another thread got through here first, we'll return their object here. 
    }

    // fallback functionality goes here if it doesn't have your attribute. 
}

はい、これにより、新しいオブジェクトが複数回作成される可能性がありますが、複数回呼び出された場合でも、呼び出し元はすべて同じ結果を受け取ります。GetOrAdd と同じです。

于 2012-03-28T03:59:19.603 に答える
3

コンピュータサイエンスのすべての問題は、別のレベルの間接化によって解決できます

// the dictionary now stores functions
private readonly ConcurrentDictionary<Type, Func<ISomeObject>> someObjectCache =
  new ConcurrentDictionary<Type, Func<ISomeObject>>();

public ISomeObject CreateSomeObject(Type someType) {
  return someObjectCache.GetOrAdd(someType, _ => {
    if(ShouldCache(someType)) {
      // caching should be used
      // return a function that returns a cached instance
      var someObject = Create(someType);
      return () => someObject; 
    }
    else {
      // no caching should be used
      // return a function that always creates a new instance
      return () => Create(someType); 
    }
  })(); // call the returned function
}

private bool ShouldCache(Type someType) {
  return Attribute.IsDefined(someType, typeof(SomeAttribute));
}

private ISomeObject Create(Type someType) {
  // typical factory functionality ...
}

ディクショナリに格納される値は関数です。キャッシュを発生させたくない場合、関数は常に新しいインスタンスを作成します。キャッシュを発生させたい場合、関数はキャッシュされたインスタンスを返します。

于 2012-04-08T02:37:03.813 に答える