優先度、混雑、依存関係、コールバックのメカニズムを備えた、機能的で軽量なCacheは、AppDomain内で動作する小規模なアプリケーションに適しています。 マイクロソフトは必要なものをすべて提供しているようです...しかし、それにもかかわらず、私はそれを少し良くしたいと思います。 なに?
同期を更新
キャッシュされたデータは、私たちの世界の多くと同様に、時間とともに関連性を失う傾向があります。 したがって、割り当てられた時間間隔の後、または依存関係の1つが変更されると、保存されたアイテムはキャッシュから消え、再びそこに置く必要があります。 MSDN がこれを行う方法を教えてくれるので、これを書きます。
List <Product> products;
products = ( List <Product>)Cache[ "Products" ];
if (products == null )
{
products = db.Products.ToList();
Cache.Insert( "Products" , products);
}
* This source code was highlighted with Source Code Highlighter .
すべては正しく見えますが、コードが複数のスレッドで同時に実行できることに気付くまでは正確です。 そして、これらの数行で、古典的な競合状態を整理しました。 もちろん、ひどいことは何も起こりません。キャッシュ要素だけが数回更新され、そのたびにデータベースにアクセスします。 しかし、これは余分な作業であり、通常のダブルチェックロックを適用することで回避できます。 このように:
private static object _lock = new object ();
...
object value;
if ((value = Cache[ "Products" ]) == null )
{
lock (_lock)
{
if ((value = Cache[ "Products" ]) == null )
{
value = db.Products.ToList();
Cache.Insert( "Products" , value);
}
}
}
var products = ( List <Product>)value;
* This source code was highlighted with Source Code Highlighter .
したがって、1つのストリームのみが商品リストのデータベースにアクセスし、残りは返品を待つことを保証します。 キャッシュを操作するときはいつでもこのようなコードを記述できますが、拡張メソッドを使用してCacheオブジェクトの拡張を実装することをお勧めします。
だから
public static T Get<T>( this Cache cache, string key, object @lock, Func<T> selector,
DateTime absoluteExpiration)
{
object value ;
if (( value = cache.Get(key)) == null )
{
lock (@lock)
{
if (( value = cache.Get(key)) == null )
{
value = selector();
cache.Insert(key, value , null ,
absoluteExpiration, Cache.NoSlidingExpiration,
CacheItemPriority.Normal, null );
}
}
}
return (T) value ;
}
* This source code was highlighted with Source Code Highlighter .
指定されたキーを持つ要素がキャッシュで見つかった場合、メソッドは単純にそれを返します。それ以外の場合、ロックを設定してロードします。 別のスレッドでキャッシュ要素を削除するときに同じ競合を取得しないために、
value = Cache.Get(key)
構造が必要です。 製品のリストを取得するために、1行のみを記述できます。残りの部分は拡張で処理します。 オーバーロードは味に追加できます:)
private static object myLock = new object() ;
...
var products = Cache.Get< List <Product>>( "Products" , myLock,
() => db.Products.ToList(), DateTime .Now.AddMinutes(10));
* This source code was highlighted with Source Code Highlighter .
したがって、1つのタスクを処理しましたが、まだ興味深いことがあります。 たとえば、いくつかの関連するキャッシュ要素を一度に無効と宣言する必要がある状況。 ASP.NET Cacheは、1つまたは複数の要素に依存関係を作成する機能を提供します。 このようなもの:
string [] dependencies = { "parent" };
Cache.Insert( "child" , someData,
new CacheDependency( null , dependencies));
* This source code was highlighted with Source Code Highlighter .
また、 親要素を更新すると、 子要素が削除されます。 これまでのところ、何も思い出しませんか? まあ、もう少しコード、そして私たちは完全になります...
タグとグループの障害サポート
タグ付けサブシステムは、既に上記で説明したキャッシュ依存性メカニズムを使用して、非常に透過的に機能します。 これらのキーは-何だと思いますか? -タグ。 タグのコレクションを持つ要素をキャッシュに追加するとき、キャッシュ内の対応する数のキーとそれらへの依存を作成します。
public static CacheDependency CreateTagDependency(
this Cache cache, params string [] tags)
{
if (tags == null || tags.Length < 1)
return null ;
long version = DateTime .UtcNow.Ticks;
for ( int i = 0; i < tags.Length; ++i)
{
cache.Add( "_tag:" + tags[i], version, null ,
DateTime .MaxValue, Cache.NoSlidingExpiration,
CacheItemPriority.NotRemovable, null );
}
return new CacheDependency( null , tags.Select(s =>
"_tag:" + s).ToArray());
}
* This source code was highlighted with Source Code Highlighter .
ここでは、 Memcachedの記事で推奨されているように、現在の時刻をタグバージョンの値として使用しますが、この場合は比較する必要がないため、ASP.NETがこれを実行します。 キャッシュにアイテムを追加するときに、指定したタグへの依存関係を簡単に作成できます。
ache.Insert( "key" , value , ache.CreateTagDependency( "tag1" , "tag2" ));
* This source code was highlighted with Source Code Highlighter .
キャッシュ内のそのような要素グループのリセットを確実にするために、ほとんど残っていません。 これを行うには、対象のタグを表すキャッシュ要素を更新するだけです。 他のすべては自然に起こります。
public static void Invalidate( this Cache cache, params string [] tags)
{
long version = DateTime .UtcNow.Ticks;
for ( int i = 0; i < tags.Length; ++i)
{
cache.Insert( "_tag:" + tags[i], version, null ,
DateTime .MaxValue, Cache.NoSlidingExpiration,
CacheItemPriority.NotRemovable, null );
}
}
* This source code was highlighted with Source Code Highlighter .
cache.Add
メソッドは
cache.Add
メソッドを使用し、ここでは
cache.Add
メソッドを使用していることに注意してください。 これらの方法の大きな違いは、指定されたキーが以前に作成されていない場合にのみ最初にキャッシュに情報を書き込むことと、いずれにしても古いデータを上書きすることであるということです。 この場合、この違いは非常に重要です。キャッシュにアイテムを追加するだけで、既存のタグを更新する必要がないためです。
それがすべてのようです...
宴会の継続をお願いします!
そして、まだまだ多くのことがあります。 たとえば、ここで説明するGetメソッドを改良して、キャッシュデータをすぐに一時的に別のセルに「移動」してブロックするのではなく、新しいデータがキャッシュにロードされている間に要求された情報を返すようにすることができます。
拡張メソッドの代わりに、キャッシュプロバイダーを抽象化して、アプリケーションコードを変更せずにストレージを操作したり、デバッグ時にキャッシュを完全に無効にしたり、IoCを使用したりできますが、気にしないでください!
そして、私の記事で説明されているアプローチがあなたに役立つことを願っています;)
更新 :私は目を閉じて自分のコードを見て、その中に1つの悪いことを見ました-同期中のロックはキャッシュ全体に設定されました。 したがって、ロック用のカスタムオブジェクトを受け入れるようにGet拡張メソッドを変更しました。