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

作成者: Rick AndersonJohn LuoSteve Smith

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

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

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

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

System.Runtime.Caching と MemoryCache

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

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

System.Runtime.Caching/MemoryCache よりも Microsoft.Extensions.Caching.Memory/IMemoryCache (この記事で説明しています) をお勧めします。理由は、ASP.NET Core への統合が強化されているためです。 たとえば、IMemoryCache は ASP.NET Core の依存関係の挿入においてネイティブに機能します。

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

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

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

IMemoryCache を使用する

警告

依存関係の挿入からの "共有" メモリ キャッシュを使用しながら、SetSizeSizeSizeLimit のいずれかを呼び出してキャッシュ サイズを制限すると、アプリにエラーが発生する可能性があります。 キャッシュにサイズ制限が設定されている場合、すべてのエントリの追加時にサイズを指定する必要があります。 これにより問題が発生する可能性があります。開発者は、共有キャッシュを何に使用するかを完全に制御できるとは限らないからです。 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 日後にキャッシュから削除されます。

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

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;
    });

上のコードでは、データが絶対時間よりも長くキャッシュされません。

GetOrCreateGetOrCreateAsyncGet は、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

MemoryCache.Compact により、指定した割合のキャッシュが次の順序で削除されます。

  • 期限切れのすべての項目。
  • 優先順位別の項目。 最も優先順位の低い項目が最初に削除されます。
  • 最も長く使用されていないオブジェクト。
  • 最も古い絶対的な有効期限を持つ項目。
  • 最も古いスライド式有効期限を持つ項目。

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

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

詳細については、「Compact source on GitHub」を参照してください

キャッシュの依存関係

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

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

その他のメモ

  • 有効期限切れはバックグラウンドでは発生しません。 有効期限が切れた項目のキャッシュをアクティブにスキャンするタイマーはありません。 キャッシュでのあらゆるアクティビティ (GetSetRemove) によって、バックグラウンドでの期限切れ項目のスキャンをトリガーできます。 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));
    
        _memoryCache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
    }
    
  • コールバックを使用してキャッシュ項目を再設定する場合:

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

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

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

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

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

その他の技術情報

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

キャッシュの基本

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

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

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

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

System.Runtime.Caching と MemoryCache

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

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

System.Runtime.Caching/MemoryCache よりも Microsoft.Extensions.Caching.Memory/IMemoryCache (この記事で説明しています) をお勧めします。理由は、ASP.NET Core への統合が強化されているためです。 たとえば、IMemoryCache は ASP.NET Core の依存関係の挿入においてネイティブに機能します。

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

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

  • コードでは常に、データをフェッチするためのフォールバック オプションを使用し、キャッシュされた値が使用可能であることに依存しないようにする必要があります。
  • キャッシュには、貴重なリソースであるメモリが使用されます。 キャッシュの拡張を制限してください。
    • 外部入力をキャッシュ キーとして使用しないでください。
    • キャッシュの拡張を制限するには、有効期限を使用します。
    • SetSize、Size、SizeLimit を使用してキャッシュ サイズを制限します。 ASP.NET Core ランタイムでメモリ負荷に基づいてキャッシュ サイズが制限されることはありません。 キャッシュ サイズを制限する決定は開発者が行います。

IMemoryCache を使用する

警告

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

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

public class HomeController : Controller
{
    private IMemoryCache _cache;

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

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

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 値は、タイムアウト期間内に要求がある間、キャッシュに残ります。

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

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);
}

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

GetOrCreateGetOrCreateAsyncGet は、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

MemoryCache.Compact により、指定した割合のキャッシュが次の順序で削除されます。

  • 期限切れのすべての項目。
  • 優先順位別の項目。 最も優先順位の低い項目が最初に削除されます。
  • 最も長く使用されていないオブジェクト。
  • 最も古い絶対的な有効期限を持つ項目。
  • 最も古いスライド式有効期限を持つ項目。

優先順位 NeverRemove の固定された項目は削除されません。 次のコードでは、キャッシュ項目を削除し、Compact を呼び出しています。

_cache.Remove(MyKey);

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

詳細については、「Compact source on GitHub」を参照してください

キャッシュの依存関係

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

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 を使用すると、複数のキャッシュ エントリを 1 つのグループとして削除できます。 上記のコードの 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);
    }
    
  • コールバックを使用してキャッシュ項目を再設定する場合:

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

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

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

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

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

その他の技術情報