ポストシャープ。 キャッシングの問題を解決します

特定の操作の操作を高速化する方法がない状況が時々あります。 外部のWebサーバーにある一部のサービスに依存する場合もあれば、プロセッサに高負荷を与える操作になる場合もあります。 または、迅速な操作である場合もありますが、それらの並列操作はコンピューターからすべてのパフォーマンスリソースを消費する可能性があります。 キャッシングを使用する理由はたくさんあります。 PostSharpは 、最初はキャッシングフレームワークのソリューションを提供しないことに注意してください。プログラムソースコード全体のキャッシングを担当するコードを設定するなどの退屈なアクションなしで、このタスクを大幅に高速化することができます。 この問題をエレガントに解決し、タスクをクラスに配置して再利用できるようにします。







カーディーラーのウェブサイトで、このカーディーラーで販売されている車がどれだけの価値があるかを知りたいとします。 これを行うには、特定のメーカー、モデル、製造年の車用に設計されたサーバーから客室の価格表をダウンロードするアプリケーションを使用します。 (この例の一部として)価格表の値が頻繁に変更される場合、Webサービスを使用してこの価格表の値を取得します。 同時に、Webサービスが遅すぎると仮定しますが、あまりにも多くの車をリクエストしたいと思います。 ご存知のように、他の人のWebサービスを高速化することはできませんが、ストアから返されたデータをキャッシュして、リクエストの数を減らすことができます。

PostSharpの主な機能の1つはメソッド呼び出しの「傍受」、つまり メソッド本体の作業の前後にコードを実行できるようにメソッドに実装する場合、このフレームワークを使用してキャッシングタスクを実装します。

[ Serializable ]

public class CacheAttribute : MethodInterceptionAspect

{

[NonSerialized]

private static readonly ICache _cache;

private string _methodName;



static CacheAttribute()

{

if (!PostSharpEnvironment.IsPostSharpRunning)

{

// one minute cache

_cache = new StaticMemoryCache( new TimeSpan (0, 1, 0));

// use an IoC container/service locator here in practice

}

}



public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)

{

_methodName = method.Name;

}



public override void OnInvoke(MethodInterceptionArgs args)

{

var key = BuildCacheKey(args.Arguments);

if (_cache[key] != null )

{

args.ReturnValue = _cache[key];

}

else

{

var returnVal = args.Invoke(args.Arguments);

args.ReturnValue = returnVal;

_cache[key] = returnVal;

}

}



private string BuildCacheKey(Arguments arguments)

{

var sb = new StringBuilder ();

sb.Append(_methodName);

foreach ( var argument in arguments.ToArray())

{

sb.Append(argument == null ? "_" : argument.ToString());

}

return sb.ToString();

}

}



* This source code was highlighted with Source Code Highlighter
.








コンパイル時にメソッド名を保存し、実行時にキャッシュサービスを初期化します。 キャッシングのキーとして、メソッドの名前と、スペースでリストされたメソッドのすべてのパラメーターの値を使用します(BuildCacheKeyメソッドのコードを参照)。これは、各メソッドと各パラメーターセットで一意になります。 OnInvokeメソッドでは、受信したキーがキャッシュに存在するかどうかを確認し、キーが既に存在する場合はキャッシュの値を使用します。 それ以外の場合は、元のメソッドのコードを呼び出して、次の呼び出しまで作業結果をキャッシュします。

私の例には、自動車に関する情報を取得するWebサービス呼び出しをシミュレートするように設計されたGetCarValueメソッドがあります。 このメソッドにはさまざまな値を取ることができるパラメーターがあるため、呼び出されるたびに異なる結果を返すことができます(この例では、キャッシュされた値がない場合のみ)。

[Cache]

public decimal GetCarValue( int year, CarMakeAndModel carType)

{

// simulate web service time

Thread.Sleep(_msToSleep);



int yearsOld = Math .Abs( DateTime .Now.Year - year);

int randomAmount = ( new Random ()).Next(0, 1000);

int calculatedValue = baselineValue - (yearDiscount*yearsOld) + randomAmount;

return calculatedValue;

}



* This source code was highlighted with Source Code Highlighter
.






この側面に関するいくつかの注意:



コンパイル時のチェック



キャッシュが良い考えではない場合、常にオプションがあります。 たとえば、メソッドがStream、IEnumerable、IQueryableなどを返す場合

インターフェース。 したがって、そのような値はキャッシュできません。 これを行うには、たとえば次のようにCompileTimeValidateメソッドをオーバーライドする必要があります。

public override bool CompileTimeValidate(MethodBase method)

{

var methodInfo = method as MethodInfo;

if (methodInfo != null )

{

var returnType = methodInfo.ReturnType;

if (IsDisallowedCacheReturnType(returnType))

{

Message.Write(SeverityType.Error, "998" ,

"Methods with return type {0} cannot be cached in {1}.{2}" ,

returnType.Name, _className, _methodName);

return false ;

}

}

return true ;

}



private static readonly IList DisallowedTypes = new List

{

typeof ( Stream ),

typeof ( IEnumerable ),

typeof (IQueryable)

};

private static bool IsDisallowedCacheReturnType(Type returnType)

{

return DisallowedTypes.Any(t => t.IsAssignableFrom(returnType));

}



* This source code was highlighted with Source Code Highlighter
.








したがって、開発者がキャッシュすべきでないメソッドにキャッシュを適用しようとすると、コンパイルエラーメッセージが表示されます。 ちなみに、IsAssignableFromを特定の型に使用する場合、それを十分に取得するクラスとインターフェイスもカバーします。 つまり 私たちの場合、FileStream、IEnumerableなどのタイプもカバーされます。



マルチタスク



素晴らしい、この時点で、それを必要とするすべてのメソッドにキャッシュを追加する優れたソリューションが既にあります。 ただし、このキャッシングの側面に隠れている潜在的な問題について考えたことはありますか? マルチタスクアプリケーション(Webサイトなど)では、最初の「ユーザー」がキャッシュにアクセスした後、後続の「ユーザー」がキャッシュアクセス速度を使用してすべてのクリームを描画するため、キャッシングは優れた仕事をします。 ただし、2人のユーザーが同じ情報を同時に取得しようとするとどうなりますか? 私たちが開発したキャッシングの側面では、これは両方のユーザーが少なくとも同じキャッシュ値を計算することを意味します。 カーショップWebサイトの場合、これはあまり重要ではありませんが、同じ情報を同時に要求する数百または数千の訪問者がいるWebサーバーがある場合、キャッシュタスクは非常に重要になります。 それらがすべて同時にリクエストを行う場合、キャッシュは常に同じ値を計算します。



この問題の簡単な解決策は、キャッシュが使用されるたびに「ロック」を使用することです。 ただし、ロックは高価で低速な操作であり、最初にキャッシュ内のキーの存在を確認してから、それをブロックするだけの方が優れています。 ただし、この場合、複数のスレッドがキャッシュ内のキーの不在を同時にチェックしてこのキーの計算に進む可能性があるため、ロックされたコードの外側とその内側でキーの存在を2回チェックする必要があります( ダブルチェックロック ):

[ Serializable ]

public class CacheAttribute : MethodInterceptionAspect

{

[NonSerialized] private object syncRoot;



public override void RuntimeInitialize(MethodBase method)

{

syncRoot = new object ();

}



public override void OnInvoke(MethodInterceptionArgs args)

{

var key = BuildCacheKey(args.Arguments);

if (_cache[key] != null )

{

args.ReturnValue = _cache[key];

}

else

{

lock (syncRoot)

{

if (_cache[key] == null )

{

var returnVal = args.Invoke(args.Arguments);

args.ReturnValue = returnVal;

_cache[key] = returnVal;

}

else

{

args.ReturnValue = _cache[key];

}

}

}

}

}



* This source code was highlighted with Source Code Highlighter
.








少し繰り返して見えますが、この場合、これは高負荷ソリューションのパフォーマンスを改善するための優れたソリューションです。 キャッシュをブロックする代わりに、アスペクトが適用されるメソッドのみに固有のプライベートオブジェクトをブロックしています。 これにより、キャッシュの使用時にロックの数が最小限に抑えられます。

あなたが混乱しないことを願っていますか? 多くのタスクの並列実行の問題は混乱を招く可能性がありますが、多くのアプリケーションではこれが現実です。 この側面で武装することで、自分自身のミスや経験の少ない開発者のミスを心配する必要がなくなります。 または、新しい開発者またはCOBOLで30年の開発経験があり、C#を初めて見た開発者の間違いについて:)。 実際、彼らはCacheアスペクトでメソッドをフレーム化する方法を知る必要があり、このテクノロジーの実装方法を知る必要はありません。 また、メソッドをスレッドセーフにする方法を知ってはなりません。 関連するタスクの実装に気を取られることなく、自分のコードだけに集中することができます。



参照:




All Articles