次の方法で共有


ASP.NET Coreのインメモリキャッシュ

リック・アンダーソンジョン・ルオスティーブ・スミス

キャッシュを使用すると、コンテンツの生成に必要な作業を減らすことで、アプリのパフォーマンスとスケーラビリティを大幅に向上させることができます。 キャッシュは、変更頻度が低く 生成にコストがかかるデータに最適です。 キャッシュは、ソースからのものよりもはるかに高速に返すことができるデータのコピーを作成します。 アプリは、キャッシュされた データに依存しないように 記述およびテストする必要があります。

ASP.NET Core は、いくつかの異なるキャッシュをサポートしています。 最も単純なキャッシュは、 IMemoryCacheに基づいています。 IMemoryCache は、Web サーバーのメモリに格納されているキャッシュを表します。 サーバー ファーム (複数のサーバー) で実行されているアプリは、メモリ内キャッシュを使用するときにセッションがスティッキーであることを確認する必要があります。 スティッキーセッションは、クライアントからの要求がすべて同じサーバーに送信されるようにします。 たとえば、Azure Web アプリでは、 アプリケーション要求ルーティング (ARR) を使用して、すべての要求を同じサーバーにルーティングします。

Web ファームの非スティッキー セッションでは、キャッシュの一貫性の問題を回避するために 分散キャッシュ が必要です。 一部のアプリでは、分散キャッシュはメモリ内キャッシュよりも高いスケールアウトをサポートできます。 分散キャッシュを使用すると、キャッシュメモリの負荷が外部プロセスにオフロードされます。

メモリ内キャッシュには、任意のオブジェクトを格納できます。 分散キャッシュ インターフェイスは byte[] に制限されています。 インメモリ キャッシュと分散キャッシュには、キャッシュ項目がキーと値のペアとして格納されます。

System.Runtime.Caching/メモリキャッシュ

System.Runtime.Caching / MemoryCache (NuGet パッケージ) は、次のものと共に使用できます。

  • .NET Standard 2.0 以降。
  • .NET Standard 2.0 以降を対象とする任意の .NET 実装 。 たとえば、ASP.NET Core 3.1 以降です。
  • .NET Framework 4.5 以降。

Microsoft.Extensions.Caching.Memory/IMemoryCache (この記事で説明) は、ASP.NET Core により適切に統合されているため、 System.Runtime.Caching/MemoryCache よりも推奨されます。 たとえば、 IMemoryCache は Core 依存関係の挿入 ASP.NET ネイティブに動作します。

System.Runtime.Caching / MemoryCache を ASP.NET 4.x から ASP.NET Core にコードを移植するときに、互換性ブリッジとして使用します。

キャッシュのガイドライン

  • コードには、データをフェッチするためのフォールバック オプションを常に 用意し、 キャッシュされた値が使用可能であることに依存しないようにする必要があります。
  • キャッシュは、希少なリソースであるメモリを使用します。 キャッシュの増加を制限します。
    • キャッシュに外部入力を挿入 しないでください 。 たとえば、任意のユーザー指定の入力をキャッシュキーとして使用することは、入力が予測できない量のメモリを消費する可能性があるため、お勧めしません。
    • 有効期限を使用して、キャッシュの増加を制限します。
    • SetSize、Size、および SizeLimit を使用して、キャッシュ サイズを制限します。 ASP.NET Core ランタイムは、メモリ負荷に基づいてキャッシュ サイズを制限し ません 。 キャッシュ サイズを制限するのは開発者の責任です。

IMemoryCache を使用する

Warnung

依存関係の挿入から共有メモリ キャッシュを使用し、SetSizeSize、または SizeLimit を呼び出してキャッシュ サイズを制限すると、アプリが失敗する可能性があります。 キャッシュにサイズ制限が設定されている場合、すべてのエントリは追加時にサイズを指定する必要があります。 これは、開発者が共有キャッシュを使用するものを完全に制御できない可能性があるため、問題を引き起こす可能性があります。 SetSizeSize、または SizeLimit を使用してキャッシュを制限する場合は、キャッシュ用のキャッシュ シングルトンを作成します。 詳細と例については 、「SetSize、Size、および SizeLimit を使用してキャッシュ サイズを制限する」を参照してください。 共有キャッシュは、他のフレームワークまたはライブラリによって共有されるキャッシュです。

インメモリ キャッシュは、依存関係の挿入を使用してアプリから参照されるサービスです。 コンストラクタで IMemoryCache インスタンスをリクエストします。

public class IndexModel : PageModel
{
    private readonly IMemoryCache _memoryCache;

    public IndexModel(IMemoryCache memoryCache) =>
        _memoryCache = memoryCache;

    // ...

次のコードでは、 TryGetValue を使用して、キャッシュに時刻があるかどうかを確認します。 時刻がキャッシュされていない場合は、新しいエントリが作成され、次の Setと共にキャッシュに追加されます。

public void OnGet()
{
    CurrentDateTime = DateTime.Now;

    if (!_memoryCache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
    {
        cacheValue = CurrentDateTime;

        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        _memoryCache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
    }

    CacheCurrentDateTime = cacheValue;
}

上記のコードでは、キャッシュ エントリは 3 秒のスライディング有効期限で構成されています。 キャッシュ エントリに 3 秒以上アクセスされない場合、キャッシュから削除されます。 キャッシュ エントリがアクセスされるたびに、キャッシュ エントリはさらに 3 秒間キャッシュに残ります。 CacheKeys クラスは、ダウンロード サンプルの一部です。

現在の時刻とキャッシュされた時刻が表示されます。

<ul>
    <li>Current Time: @Model.CurrentDateTime</li>
    <li>Cached Time: @Model.CacheCurrentDateTime</li>
</ul>

次のコードでは、 Set 拡張メソッドを使用して、 MemoryCacheEntryOptionsなしで相対時間データをキャッシュします。

_memoryCache.Set(CacheKeys.Entry, DateTime.Now, TimeSpan.FromDays(1));

上記のコードでは、キャッシュ エントリは相対的な有効期限が 1 日に設定されています。 キャッシュエントリは、このタイムアウト期間内にアクセスされた場合でも、1日後にキャッシュから削除されます。

次のコードでは、 GetOrCreateGetOrCreateAsync を使用してデータをキャッシュします。

public void OnGetCacheGetOrCreate()
{
    var cachedValue = _memoryCache.GetOrCreate(
        CacheKeys.Entry,
        cacheEntry =>
        {
            cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
            return DateTime.Now;
        });

    // ...
}

public async Task OnGetCacheGetOrCreateAsync()
{
    var cachedValue = await _memoryCache.GetOrCreateAsync(
        CacheKeys.Entry,
        cacheEntry =>
        {
            cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
            return Task.FromResult(DateTime.Now);
        });

    // ...
}

次のコードは、 Get を呼び出してキャッシュされた時間をフェッチします。

var cacheEntry = _memoryCache.Get<DateTime?>(CacheKeys.Entry);

次のコードは、絶対有効期限を持つキャッシュされた項目を取得または作成します。

var cachedValue = _memoryCache.GetOrCreate(
    CacheKeys.Entry,
    cacheEntry =>
    {
        cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
        return DateTime.Now;
    });

スライディング有効期限のみを持つキャッシュされた項目セットは、期限切れにならないリスクがあります。 キャッシュされた項目がスライド式の有効期限間隔内に繰り返しアクセスされる場合、項目は期限切れになりません。 スライド式の有効期限と絶対的な有効期限を組み合わせて、アイテムの有効期限を確保します。 絶対有効期限は、アイテムをキャッシュできる期間の上限を設定しますが、スライド式有効期限間隔内にアイテムが要求されない場合は、アイテムの有効期限を早めることができます。 スライディング有効期限間隔 または 絶対有効期限が経過すると、アイテムはキャッシュから削除されます。

次のコードは、スライディング有効期限 絶対有効期限の両方を持つキャッシュされた項目を取得または作成します。

var cachedValue = _memoryCache.GetOrCreate(
    CacheKeys.CallbackEntry,
    cacheEntry =>
    {
        cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
        cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
        return DateTime.Now;
    });

上記のコードでは、データが絶対時間より長くキャッシュされないことが保証されます。

GetOrCreateGetOrCreateAsync、および Get は、 CacheExtensions クラスの拡張メソッドです。 これらの方法は、 IMemoryCacheの機能を拡張します。

MemoryCacheEntryOptions

次のような例です。

  • キャッシュの優先度を CacheItemPriority.NeverRemove に設定します。
  • エントリがキャッシュから削除された後に呼び出される PostEvictionDelegate を設定します。 コールバックは、キャッシュから項目を削除するコードとは異なるスレッドで実行されます。
public void OnGetCacheRegisterPostEvictionCallback()
{
    var memoryCacheEntryOptions = new MemoryCacheEntryOptions()
        .SetPriority(CacheItemPriority.NeverRemove)
        .RegisterPostEvictionCallback(PostEvictionCallback, _memoryCache);

    _memoryCache.Set(CacheKeys.CallbackEntry, DateTime.Now, memoryCacheEntryOptions);
}

private static void PostEvictionCallback(
    object cacheKey, object cacheValue, EvictionReason evictionReason, object state)
{
    var memoryCache = (IMemoryCache)state;

    memoryCache.Set(
        CacheKeys.CallbackMessage,
        $"Entry {cacheKey} was evicted: {evictionReason}.");
}

SetSize、Size、SizeLimit を使用してキャッシュ サイズを制限する

MemoryCacheインスタンスでは、オプションでサイズ制限を指定して適用できます。 キャッシュにはエントリのサイズを測定するメカニズムがないため、キャッシュ サイズの制限には測定単位が定義されていません。 キャッシュ・サイズ制限が設定されている場合、すべてのエントリーでサイズを指定する必要があります。 ASP.NET Core ランタイムでは、メモリ負荷に基づいてキャッシュ サイズが制限されることはありません。 キャッシュ サイズを制限するのは開発者の責任です。 指定されるサイズは、開発者が選択した単位です。

例えば次が挙げられます。

  • Web アプリが主に文字列をキャッシュしていた場合、各キャッシュ エントリ サイズを文字列の長さにすることができます。
  • アプリでは、すべてのエントリのサイズを 1 に指定でき、サイズ制限はエントリの数です。

SizeLimitが設定されていない場合、キャッシュは無制限に拡張されます。 ASP.NET Core ランタイムは、システム メモリが不足しているときにキャッシュをトリミングしません。 アプリは、次のように構成する必要があります。

  • キャッシュの増加を制限します。
  • 使用可能なメモリが限られている場合は、 Compact または Remove に電話してください。

次のコードは、MemoryCacheによってアクセス可能な単位のない固定サイズを作成します。

public class MyMemoryCache
{
    public MemoryCache Cache { get; } = new MemoryCache(
        new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
}

SizeLimit ユニットはありません。 キャッシュされたエントリは、キャッシュ サイズの制限が設定されている場合、最も適切と思われる単位でサイズを指定する必要があります。 キャッシュ・インスタンスのすべてのユーザーは、同じ単位システムを使用する必要があります。 キャッシュされたエントリ サイズの合計が SizeLimit で指定された値を超える場合、エントリはキャッシュされません。 キャッシュ・サイズの制限が設定されていない場合、エントリーに設定されたキャッシュ・サイズは無視されます。

次のコードは、MyMemoryCacheコンテナーに を登録します。

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddSingleton<MyMemoryCache>();

MyMemoryCache は、このサイズ制限付きキャッシュを認識し、キャッシュ エントリ サイズを適切に設定する方法を認識しているコンポーネント用の独立したメモリ キャッシュとして作成されます。

キャッシュエントリのサイズは、 SetSize 拡張メソッドまたは Size プロパティを使用して設定できます。

if (!_myMemoryCache.Cache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
{
    var cacheEntryOptions = new MemoryCacheEntryOptions()
        .SetSize(1);

    // cacheEntryOptions.Size = 1;

    _myMemoryCache.Cache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
}

上記のコードでは、強調表示された 2 つの行で、キャッシュ エントリのサイズを設定するのと同じ結果が得られます。 SetSize は、 new MemoryCacheOptions()に呼び出しをチェーンする場合に便利です。

メモリキャッシュ.コンパクト

MemoryCache.Compact は、指定された割合のキャッシュの削除を次の順序で試行します。

  • すべての期限切れのアイテム。
  • 優先度別の項目。 最も優先度の低いアイテムが最初に削除されます。
  • 最も長く使用されていないオブジェクト。
  • 絶対有効期限が最も早いアイテム。
  • スライド有効期限が最も早いアイテム。

優先 NeverRemove でピン留めされたアイテムは 削除されません。 次のコードでは、キャッシュ項目を削除し、 Compact を呼び出して 25% のキャッシュされたエントリを削除します。

_myMemoryCache.Cache.Remove(CacheKeys.Entry);
_myMemoryCache.Cache.Compact(.25);

詳細については、 GitHub の Compact ソースを参照してください。

キャッシュの依存関係

次のサンプルは、依存エントリの有効期限が切れた場合にキャッシュ エントリを期限切れにする方法を示しています。 キャッシュされたアイテムに CancellationChangeToken が追加されます。 CancelCancellationTokenSource が呼び出されると、両方のキャッシュ エントリが削除されます。

public void OnGetCacheCreateDependent()
{
    var cancellationTokenSource = new CancellationTokenSource();

    _memoryCache.Set(
        CacheKeys.DependentCancellationTokenSource,
        cancellationTokenSource);

    using var parentCacheEntry = _memoryCache.CreateEntry(CacheKeys.Parent);

    parentCacheEntry.Value = DateTime.Now;

    _memoryCache.Set(
        CacheKeys.Child,
        DateTime.Now,
        new CancellationChangeToken(cancellationTokenSource.Token));
}

public void OnGetCacheRemoveDependent()
{
    var cancellationTokenSource = _memoryCache.Get<CancellationTokenSource>(
        CacheKeys.DependentCancellationTokenSource);

    cancellationTokenSource.Cancel();
}

CancellationTokenSourceを使用すると、複数のキャッシュエントリをグループとして削除できます。 上記のコードの using パターンでは、 using スコープ内に作成されたキャッシュ エントリは、トリガーと有効期限の設定を継承します。

追加メモ

  • 有効期限はバックグラウンドでは発生しません。 期限切れの項目のキャッシュをアクティブにスキャンするタイマーはありません。 キャッシュ上のアクティビティ (GetTryGetValueSetRemove) は、期限切れのアイテムのバックグラウンド スキャンをトリガーできます。 CancellationTokenSource (CancelAfter) のタイマーもエントリを削除し、期限切れのアイテムのスキャンをトリガーします。 次の例では、登録済みのトークンに CancellationTokenSource(TimeSpan) を使用しています。 このトークンが起動すると、エントリはすぐに削除され、エビクションコールバックが起動します。

    if (!_memoryCache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
    {
        cacheValue = DateTime.Now;
    
        var cancellationTokenSource = new CancellationTokenSource(
            TimeSpan.FromSeconds(10));
    
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .AddExpirationToken(
                new CancellationChangeToken(cancellationTokenSource.Token))
            .RegisterPostEvictionCallback((key, value, reason, state) =>
            {
                ((CancellationTokenSource)state).Dispose();
            }, cancellationTokenSource);
    
        _memoryCache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
    }
    
  • コールバックを使用してキャッシュ項目を再設定する場合:

    • 複数のリクエストでは、コールバックが完了していないため、キャッシュされたキー値が空である可能性があります。
    • これにより、キャッシュされた項目に複数のスレッドが再入力される可能性があります。
  • 1 つのキャッシュ エントリを使用して別のキャッシュ エントリを作成すると、子は親エントリの有効期限トークンと時間ベースの有効期限設定をコピーします。 親エントリを手動で削除または更新しても、子の有効期限が切れることはありません。

  • PostEvictionCallbacks を使用して、キャッシュエントリがキャッシュから削除された後に発生するコールバックを設定します。

  • ほとんどのアプリでは、 IMemoryCache が有効になっています。 たとえば、 AddMvcAddControllersWithViewsAddRazorPagesAddMvcCore().AddRazorViewEngine、およびその他の多くの Add{Service} メソッドを Program.csで呼び出すと、 IMemoryCacheが可能になります。 上記の Add{Service}メソッドのいずれも呼び出さないアプリの場合は、AddMemoryCacheProgram.cs を呼び出す必要がある場合があります。

バックグラウンド キャッシュの更新

などのIHostedServiceを使用して、キャッシュを更新します。 バックグラウンド サービスは、エントリを再計算し、準備ができたときにのみキャッシュに割り当てることができます。

その他のリソース

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

キャッシングの基本

キャッシュを使用すると、コンテンツの生成に必要な作業を減らすことで、アプリのパフォーマンスとスケーラビリティを大幅に向上させることができます。 キャッシュは、変更頻度が低く 生成にコストがかかるデータに最適です。 キャッシュは、ソースからのものよりもはるかに高速に返すことができるデータのコピーを作成します。 アプリは、キャッシュされた データに依存しないように 記述およびテストする必要があります。

ASP.NET Core は、いくつかの異なるキャッシュをサポートしています。 最も単純なキャッシュは、 IMemoryCacheに基づいています。 IMemoryCache は、Web サーバーのメモリに格納されているキャッシュを表します。 サーバー ファーム (複数のサーバー) で実行されているアプリは、メモリ内キャッシュを使用するときにセッションがスティッキーであることを確認する必要があります。 スティッキーセッションは、クライアントからの後続の要求がすべて同じサーバーに送信されることを保証します。 たとえば、Azure Web アプリでは、 アプリケーション要求ルーティング (ARR) を使用して、後続のすべての要求を同じサーバーにルーティングします。

Web ファームの非スティッキー セッションでは、キャッシュの一貫性の問題を回避するために 分散キャッシュ が必要です。 一部のアプリでは、分散キャッシュはメモリ内キャッシュよりも高いスケールアウトをサポートできます。 分散キャッシュを使用すると、キャッシュメモリの負荷が外部プロセスにオフロードされます。

メモリ内キャッシュには、任意のオブジェクトを格納できます。 分散キャッシュ インターフェイスは byte[] に制限されています。 インメモリ キャッシュと分散キャッシュには、キャッシュ項目がキーと値のペアとして格納されます。

System.Runtime.Caching/メモリキャッシュ

System.Runtime.Caching / MemoryCache (NuGet パッケージ) は、次のものと共に使用できます。

  • .NET Standard 2.0 以降。
  • .NET Standard 2.0 以降を対象とする任意の .NET 実装 。 たとえば、ASP.NET Core 3.1 以降です。
  • .NET Framework 4.5 以降。

Microsoft.Extensions.Caching.Memory/IMemoryCache (この記事で説明) は、ASP.NET Core により適切に統合されているため、 System.Runtime.Caching/MemoryCache よりも推奨されます。 たとえば、 IMemoryCache は Core 依存関係の挿入 ASP.NET ネイティブに動作します。

System.Runtime.Caching / MemoryCache を ASP.NET 4.x から ASP.NET Core にコードを移植するときに、互換性ブリッジとして使用します。

キャッシュのガイドライン

  • コードには、データをフェッチするためのフォールバック オプションを常に 用意し、 キャッシュされた値が使用可能であることに依存しないようにする必要があります。
  • キャッシュは、希少なリソースであるメモリを使用します。 キャッシュの増加を制限します。

IMemoryCache を使用する

Warnung

依存関係の挿入から共有メモリ キャッシュを使用し、SetSizeSize、または SizeLimit を呼び出してキャッシュ サイズを制限すると、アプリが失敗する可能性があります。 キャッシュにサイズ制限が設定されている場合、すべてのエントリは追加時にサイズを指定する必要があります。 これは、開発者が共有キャッシュを使用するものを完全に制御できない可能性があるため、問題を引き起こす可能性があります。 SetSizeSize、または SizeLimit を使用してキャッシュを制限する場合は、キャッシュ用のキャッシュ シングルトンを作成します。 詳細と例については 、「SetSize、Size、および SizeLimit を使用してキャッシュ サイズを制限する」を参照してください。 共有キャッシュは、他のフレームワークまたはライブラリによって共有されるキャッシュです。

インメモリ キャッシュは、依存関係の挿入を使用してアプリから参照されるサービスです。 コンストラクタで IMemoryCache インスタンスをリクエストします。

public class HomeController : Controller
{
    private IMemoryCache _cache;

    public HomeController(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }

次のコードでは、 TryGetValue を使用して、キャッシュに時刻があるかどうかを確認します。 時間がキャッシュされていない場合は、新しいエントリが作成され、 Setとともにキャッシュに追加されます。 CacheKeys クラスは、ダウンロード サンプルの一部です。

public static class CacheKeys
{
    public static string Entry => "_Entry";
    public static string CallbackEntry => "_Callback";
    public static string CallbackMessage => "_CallbackMessage";
    public static string Parent => "_Parent";
    public static string Child => "_Child";
    public static string DependentMessage => "_DependentMessage";
    public static string DependentCTS => "_DependentCTS";
    public static string Ticks => "_Ticks";
    public static string CancelMsg => "_CancelMsg";
    public static string CancelTokenSource => "_CancelTokenSource";
}
public IActionResult CacheTryGetValueSet()
{
    DateTime cacheEntry;

    // Look for cache key.
    if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
    {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now;

        // Set cache options.
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            // Keep in cache for this time, reset time if accessed.
            .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        // Save data in cache.
        _cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
    }

    return View("Cache", cacheEntry);
}

現在の時刻とキャッシュされた時刻が表示されます。

@model DateTime?

<div>
    <h2>Actions</h2>
    <ul>
        <li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li>
        <li><a asp-controller="Home" asp-action="CacheGet">Get</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreateAsynchronous">CacheGetOrCreateAsynchronous</a></li>
        <li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreateAbs">CacheGetOrCreateAbs</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreateAbsSliding">CacheGetOrCreateAbsSliding</a></li>

    </ul>
</div>

<h3>Current Time: @DateTime.Now.TimeOfDay.ToString()</h3>
<h3>Cached Time: @(Model == null ? "No cached entry found" : Model.Value.TimeOfDay.ToString())</h3>

次のコードでは、 Set 拡張メソッドを使用して、 MemoryCacheEntryOptions オブジェクトを作成せずに相対時間データをキャッシュします。

public IActionResult SetCacheRelativeExpiration()
{
    DateTime cacheEntry;

    // Look for cache key.
    if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
    {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now;

        // Save data in cache and set the relative expiration time to one day
        _cache.Set(CacheKeys.Entry, cacheEntry, TimeSpan.FromDays(1));
    }

    return View("Cache", cacheEntry);
}

キャッシュされた DateTime 値は、タイムアウト期間内に要求がある間、キャッシュに残ります。

次のコードでは、 GetOrCreateGetOrCreateAsync を使用してデータをキャッシュします。

public IActionResult CacheGetOrCreate()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
    {
        entry.SlidingExpiration = TimeSpan.FromSeconds(3);
        return DateTime.Now;
    });

    return View("Cache", cacheEntry);
}

public async Task<IActionResult> CacheGetOrCreateAsynchronous()
{
    var cacheEntry = await
        _cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
        {
            entry.SlidingExpiration = TimeSpan.FromSeconds(3);
            return Task.FromResult(DateTime.Now);
        });

    return View("Cache", cacheEntry);
}

次のコードは、 Get を呼び出してキャッシュされた時間をフェッチします。

public IActionResult CacheGet()
{
    var cacheEntry = _cache.Get<DateTime?>(CacheKeys.Entry);
    return View("Cache", cacheEntry);
}

次のコードは、絶対有効期限を持つキャッシュされた項目を取得または作成します。

public IActionResult CacheGetOrCreateAbs()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
        return DateTime.Now;
    });

    return View("Cache", cacheEntry);
}

スライディング有効期限のみを持つキャッシュされた項目セットは、期限切れにならないリスクがあります。 キャッシュされた項目がスライド式の有効期限間隔内に繰り返しアクセスされる場合、項目は期限切れになりません。 スライド式の有効期限と絶対的な有効期限を組み合わせて、アイテムの有効期限を確保します。 絶対有効期限は、アイテムをキャッシュできる期間の上限を設定しますが、スライド式有効期限間隔内にアイテムが要求されない場合は、アイテムの有効期限を早めることができます。 スライディング有効期限間隔 または 絶対有効期限が経過すると、アイテムはキャッシュから削除されます。

次のコードは、スライディング有効期限 絶対有効期限の両方を持つキャッシュされた項目を取得または作成します。

public IActionResult CacheGetOrCreateAbsSliding()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
    {
        entry.SetSlidingExpiration(TimeSpan.FromSeconds(3));
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
        return DateTime.Now;
    });

    return View("Cache", cacheEntry);
}

上記のコードでは、データが絶対時間より長くキャッシュされないことが保証されます。

GetOrCreateGetOrCreateAsync、および Get は、 CacheExtensions クラスの拡張メソッドです。 これらの方法は、 IMemoryCacheの機能を拡張します。

MemoryCacheEntryOptions

次のサンプル:

  • スライド式の有効期限を設定します。 このキャッシュされた項目にアクセスする要求は、スライディング有効期限クロックをリセットします。
  • キャッシュの優先度を CacheItemPriority.NeverRemove に設定します。
  • エントリがキャッシュから削除された後に呼び出される PostEvictionDelegate を設定します。 コールバックは、キャッシュから項目を削除するコードとは異なるスレッドで実行されます。
public IActionResult CreateCallbackEntry()
{
    var cacheEntryOptions = new MemoryCacheEntryOptions()
        // Pin to cache.
        .SetPriority(CacheItemPriority.NeverRemove)
        // Add eviction callback
        .RegisterPostEvictionCallback(callback: EvictionCallback, state: this);

    _cache.Set(CacheKeys.CallbackEntry, DateTime.Now, cacheEntryOptions);

    return RedirectToAction("GetCallbackEntry");
}

public IActionResult GetCallbackEntry()
{
    return View("Callback", new CallbackViewModel
    {
        CachedTime = _cache.Get<DateTime?>(CacheKeys.CallbackEntry),
        Message = _cache.Get<string>(CacheKeys.CallbackMessage)
    });
}

public IActionResult RemoveCallbackEntry()
{
    _cache.Remove(CacheKeys.CallbackEntry);
    return RedirectToAction("GetCallbackEntry");
}

private static void EvictionCallback(object key, object value,
    EvictionReason reason, object state)
{
    var message = $"Entry was evicted. Reason: {reason}.";
    ((HomeController)state)._cache.Set(CacheKeys.CallbackMessage, message);
}

SetSize、Size、SizeLimit を使用してキャッシュ サイズを制限する

MemoryCacheインスタンスでは、オプションでサイズ制限を指定して適用できます。 キャッシュにはエントリのサイズを測定するメカニズムがないため、キャッシュサイズの制限には測定単位が定義されていません。 キャッシュ・サイズ制限が設定されている場合、すべてのエントリーでサイズを指定する必要があります。 ASP.NET Core ランタイムは、メモリ負荷に基づいてキャッシュ サイズを制限しません。 キャッシュ サイズを制限するのは開発者の責任です。 指定されるサイズは、開発者が選択した単位です。

例えば次が挙げられます。

  • Web アプリが主に文字列をキャッシュしていた場合、各キャッシュ エントリ サイズを文字列の長さにすることができます。
  • アプリでは、すべてのエントリのサイズを 1 に指定でき、サイズ制限はエントリの数です。

SizeLimitが設定されていない場合、キャッシュは無制限に拡張されます。 ASP.NET Core ランタイムは、システム メモリが不足しているときにキャッシュをトリミングしません。 アプリは、次のように構成する必要があります。

  • キャッシュの増加を制限します。
  • 使用可能なメモリが限られている場合に Compact または Remove を呼び出します。

次のコードは、MemoryCacheによってアクセス可能な単位のない固定サイズを作成します。

// using Microsoft.Extensions.Caching.Memory;
public class MyMemoryCache 
{
    public MemoryCache Cache { get; private set; }
    public MyMemoryCache()
    {
        Cache = new MemoryCache(new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
    }
}

SizeLimit ユニットはありません。 キャッシュされたエントリは、キャッシュ サイズの制限が設定されている場合、最も適切と思われる単位でサイズを指定する必要があります。 キャッシュ・インスタンスのすべてのユーザーは、同じ単位システムを使用する必要があります。 キャッシュされたエントリ サイズの合計が SizeLimit で指定された値を超える場合、エントリはキャッシュされません。 キャッシュ・サイズの制限が設定されていない場合、エントリーに設定されたキャッシュ・サイズは無視されます。

次のコードは、MyMemoryCacheコンテナーに を登録します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddSingleton<MyMemoryCache>();
}

MyMemoryCache は、このサイズ制限付きキャッシュを認識し、キャッシュ エントリ サイズを適切に設定する方法を認識しているコンポーネント用の独立したメモリ キャッシュとして作成されます。

次のコードでは MyMemoryCache を使用します。

public class SetSize : PageModel
{
    private MemoryCache _cache;
    public static readonly string MyKey = "_MyKey";

    public SetSize(MyMemoryCache memoryCache)
    {
        _cache = memoryCache.Cache;
    }

    [TempData]
    public string DateTime_Now { get; set; }

    public IActionResult OnGet()
    {
        if (!_cache.TryGetValue(MyKey, out string cacheEntry))
        {
            // Key not in cache, so get data.
            cacheEntry = DateTime.Now.TimeOfDay.ToString();

            var cacheEntryOptions = new MemoryCacheEntryOptions()
                // Set cache entry size by extension method.
                .SetSize(1)
                // Keep in cache for this time, reset time if accessed.
                .SetSlidingExpiration(TimeSpan.FromSeconds(3));

            // Set cache entry size via property.
            // cacheEntryOptions.Size = 1;

            // Save data in cache.
            _cache.Set(MyKey, cacheEntry, cacheEntryOptions);
        }

        DateTime_Now = cacheEntry;

        return RedirectToPage("./Index");
    }
}

キャッシュエントリのサイズは、 Size または SetSize 拡張メソッドで設定できます。

public IActionResult OnGet()
{
    if (!_cache.TryGetValue(MyKey, out string cacheEntry))
    {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now.TimeOfDay.ToString();

        var cacheEntryOptions = new MemoryCacheEntryOptions()
            // Set cache entry size by extension method.
            .SetSize(1)
            // Keep in cache for this time, reset time if accessed.
            .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        // Set cache entry size via property.
        // cacheEntryOptions.Size = 1;

        // Save data in cache.
        _cache.Set(MyKey, cacheEntry, cacheEntryOptions);
    }

    DateTime_Now = cacheEntry;

    return RedirectToPage("./Index");
}

メモリキャッシュ.コンパクト

MemoryCache.Compact は、指定された割合のキャッシュの削除を次の順序で試行します。

  • すべての期限切れのアイテム。
  • 優先度別の項目。 最も優先度の低いアイテムが最初に削除されます。
  • 最も長く使用されていないオブジェクト。
  • 絶対有効期限が最も早いアイテム。
  • スライド有効期限が最も早いアイテム。

優先 NeverRemove ピン留めされたアイテムは削除されません。 次のコードは、キャッシュ項目を削除し、 Compactを呼び出します。

_cache.Remove(MyKey);

// Remove 33% of cached items.
_cache.Compact(.33);   
cache_size = _cache.Count;

詳細については、 GitHub の Compact ソースを参照してください。

キャッシュの依存関係

次のサンプルは、依存エントリの有効期限が切れた場合にキャッシュ エントリを期限切れにする方法を示しています。 キャッシュされたアイテムに CancellationChangeToken が追加されます。 CancelCancellationTokenSource が呼び出されると、両方のキャッシュ エントリが削除されます。

public IActionResult CreateDependentEntries()
{
    var cts = new CancellationTokenSource();
    _cache.Set(CacheKeys.DependentCTS, cts);

    using (var entry = _cache.CreateEntry(CacheKeys.Parent))
    {
        // expire this entry if the dependant entry expires.
        entry.Value = DateTime.Now;
        entry.RegisterPostEvictionCallback(DependentEvictionCallback, this);

        _cache.Set(CacheKeys.Child,
            DateTime.Now,
            new CancellationChangeToken(cts.Token));
    }

    return RedirectToAction("GetDependentEntries");
}

public IActionResult GetDependentEntries()
{
    return View("Dependent", new DependentViewModel
    {
        ParentCachedTime = _cache.Get<DateTime?>(CacheKeys.Parent),
        ChildCachedTime = _cache.Get<DateTime?>(CacheKeys.Child),
        Message = _cache.Get<string>(CacheKeys.DependentMessage)
    });
}

public IActionResult RemoveChildEntry()
{
    _cache.Get<CancellationTokenSource>(CacheKeys.DependentCTS).Cancel();
    return RedirectToAction("GetDependentEntries");
}

private static void DependentEvictionCallback(object key, object value,
    EvictionReason reason, object state)
{
    var message = $"Parent entry was evicted. Reason: {reason}.";
    ((HomeController)state)._cache.Set(CacheKeys.DependentMessage, message);
}

CancellationTokenSourceを使用すると、複数のキャッシュエントリをグループとして削除できます。 上記のコードの using パターンを使用すると、 using ブロック内に作成されたキャッシュ エントリは、トリガーと有効期限の設定を継承します。

追加メモ

  • 有効期限はバックグラウンドでは発生しません。 期限切れの項目のキャッシュをアクティブにスキャンするタイマーはありません。 キャッシュ上のアクティビティ (GetSetRemove) は、期限切れのアイテムのバックグラウンド スキャンをトリガーできます。 CancellationTokenSource (CancelAfter) のタイマーもエントリを削除し、期限切れのアイテムのスキャンをトリガーします。 次の例では、登録済みのトークンに CancellationTokenSource(TimeSpan) を使用しています。 このトークンが発火すると、エントリはすぐに削除され、エビクションコールバックが発火します。

    public IActionResult CacheAutoExpiringTryGetValueSet()
    {
        DateTime cacheEntry;
    
        if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
        {
            cacheEntry = DateTime.Now;
    
            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
    
            var cacheEntryOptions = new MemoryCacheEntryOptions()
                .AddExpirationToken(new CancellationChangeToken(cts.Token));
    
            _cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
        }
    
        return View("Cache", cacheEntry);
    }
    
  • コールバックを使用してキャッシュ項目を再設定する場合:

    • 複数のリクエストでは、コールバックが完了していないため、キャッシュされたキー値が空である可能性があります。
    • これにより、キャッシュされた項目に複数のスレッドが再入力される可能性があります。
  • 1 つのキャッシュ エントリを使用して別のキャッシュ エントリを作成すると、子は親エントリの有効期限トークンと時間ベースの有効期限設定をコピーします。 親エントリを手動で削除または更新しても、子の有効期限が切れることはありません。

  • PostEvictionCallbacks を使用して、キャッシュエントリがキャッシュから削除された後に発生するコールバックを設定します。 このコード例では、CancellationTokenSource.Dispose() によって使用されているアンマネージド リソースを解放するために CancellationTokenSource が呼び出されます。 ただし、 CancellationTokenSource はまだキャッシュ エントリによって使用されているため、すぐには破棄されません。 CancellationTokenMemoryCacheEntryOptions に渡され、一定時間後に有効期限が切れるキャッシュ エントリが作成されます。 したがって、キャッシュエントリが削除されるか期限切れになるまで、 Dispose を呼び出さないでください。 このコード例では、 RegisterPostEvictionCallback メソッドを呼び出して、キャッシュ エントリが削除されたときに呼び出されるコールバックを登録し、そのコールバックの CancellationTokenSource を破棄します。

  • ほとんどのアプリでは、 IMemoryCache が有効になっています。 たとえば、 AddMvcAddControllersWithViewsAddRazorPagesAddMvcCore().AddRazorViewEngine、およびその他の多くの Add{Service} メソッドを ConfigureServicesで呼び出すと、 IMemoryCacheが可能になります。 上記の Add{Service} メソッドのいずれかを呼び出していないアプリの場合は、AddMemoryCacheConfigureServices を呼び出す必要がある場合があります。

バックグラウンド キャッシュの更新

などのIHostedServiceを使用して、キャッシュを更新します。 バックグラウンド サービスは、エントリを再計算し、準備ができたときにのみキャッシュに割り当てることができます。

その他のリソース