ASP.NET Core 中的記憶體中快取

由 里克·安德森、 約翰·羅和 史蒂夫·史密斯

快取可藉由減少產生內容所需的工作,大幅改善應用程式的效能和延展性。 快取最適合不常變更 且 產生成本高昂的數據。 快取讓資料副本的返回速度比直接從來源取得快得多。 應用程式應該撰寫並測試, 永遠不會 相依於快取的數據。

ASP.NET Core 支援數個不同的快取。 最簡單的快取是基於介面 IMemoryCache 。 IMemoryCache 表示儲存在網頁伺服器記憶體中的快取。 在伺服器農場(多部伺服器)上執行的應用程式應該在使用記憶體內部快取時確保會話持續連線。 黏性會話可確保來自用戶端的要求全都移至相同的伺服器。 例如,Azure 網頁應用程式使用 Microsoft Application Request Routing (ARR) 將所有請求路由至同一伺服器。

網路農場中的非黏性會話需要分散式 快取 以避免快取一致性問題。 對於某些應用程式,分散式快取可以支援比記憶體內部快取更高的向外延展。 使用分散式快取會將快取記憶體卸除至外部進程。

記憶體內部快取可以儲存任何物件。 分散式快取介面限制為 byte[]。 記憶體內部和分散式快取會將快取項目儲存為鍵值對。

使用 System.Runtime.Caching/MemoryCache

System.Runtime.Caching / MemoryCache (NuGet 套件) 可以搭配使用:

  • .NET 標準 2.0 或更新版本
  • 任何 .NET 實作 針對 .NET Standard 2.0 或更高版本(例如 ASP.NET Core 3.1 或更高版本)
  • .NET Framework 4.5 或更新版本

Microsoft。本文中介紹的擴充功能(Extensions.Caching.Memory/IMemoryCache)比 System.Runtime.Caching/MemoryCache 更推薦,因為它與 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 實例

記憶體內快取是一種應用程式透過依賴注入來引用的服務。

Warning

如果同一個快取被多個框架或函式庫使用,那就是 共享 快取。 如果你使用依賴 注入 的共享記憶體快取,並且使用 SetSize、 Size,以及 SizeLimit 為了限制快取大小,應用程式可能會失敗。

當快取設定大小限制時,所有條目在新增時必須指定大小。 這種做法可能會帶來問題,因為開發者可能無法完全控制使用共享快取的內容。

為了限制快取大小,可以使用SetSize方法,Size屬性,或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));

在上述程式代碼中,快取項目會設定為一天的相對到期日。 即使該快取條目在逾時期間被存取,也會在一天後從快取中移除。

以下程式碼使用 and 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;
    });

前述程式碼保證資料的快取時間不會超過絕對時間。

GetOrCreate、 GetOrCreateAsync和 Get 是類別中的 CacheExtensions 擴充方法。 這些方法會擴充 IMemoryCache 的功能。

為一個條目建立 MemoryCacheEntryOptions

以下範例示範如何為條目創建 MemoryCacheEntryOptions 。 程式代碼會完成下列工作:

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 執行時間不會根據記憶體壓力限制快取大小。 由開發人員決定限制快取大小。 指定的大小是以開發人員選擇的單位來指定。

例如:

  • 如果網頁應用程式主要快取字串,每個快取條目大小可能是字串長度。
  • 應用程式可以將所有條目的大小指定為 1,大小限制則是條目的數量。

如果SizeLimit屬性未被設定,快取會無限成長。 ASP.NET Core 執行時不會在系統記憶體不足時清理快取。 應用程式必須架構為:

  • 限制快取成長。
  • 當可用記憶體有限時,呼叫 Compact OR 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);
}

在上述程式代碼中,兩行醒目提示的代碼達成了相同的效果,設定快取項目的大小。 SetSize此方法的提供是為了方便地將呼叫串接到new MemoryCacheOptions()。

使用 MemoryCache.Compact 移除快取項目

此 MemoryCache.Compact 方法嘗試依以下順序移除指定百分比的快取:

  • 所有過期商品
  • 依優先順序分類項目,優先移除優先權最低的項目
  • 最近最少使用的物件
  • 最早絕對有效期的項目
  • 最早滑動到期的項目

具有優先權 NeverRemove 的釘選項目 永遠不會移除。 以下程式碼移除快取項目,並呼叫 Compact 移除 25% 快取條目的方法:

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

如需詳細資訊,請參閱 GitHub 上的 Compact 來源。

移除依賴過期的快取項目

下列範例示範如果相依專案到期,如何讓快取專案過期。 一個 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 物件時,可以作為一個群組逐出多個快取項目。 在前述程式碼中使用 using 模式,在 using 範圍內建立的快取條目會繼承觸發器與過期設定。

關於記憶體緩存的檢閱備忘錄

以下說明適用於記憶體內快取:

  • 到期不會在背景發生。

    沒有定時器會主動掃描快取中的過期項目。 快取上的任何活動(透過 Get、TryGetValue、Set 或 Remove)都可能觸發背景掃描以尋找過期的物品。 在 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);
    }
    
  • 當你使用回調來重新填入快取項目時:

    • 多次請求可能會發現快取的金鑰值為空,因為回呼尚未完成。
    • 這種做法可能導致多個執行緒重新填充快取項目。
  • 當一個快取項目(父項目)建立另一個項目(子節點)時,子節點會複製父項目的到期標記和基於時間的到期設定。 因手動移除或更新父條目,子節點不會過期。

  • 使用PostEvictionCallbacks 屬性來指定在快取項目被移除後應該執行哪些回調。

  • 針對大部分的應用程式, IMemoryCache 會啟用 。 例如,呼叫 AddMvc、 AddControllersWithViews、 AddRazorPages、 AddMvcCore().AddRazorViewEngine以及 Add{Service} 檔案中許多其他方法可啟用 IMemoryCache。

    對於沒有呼叫上述Add{Service}方法的應用程式,你可能需要在 AddMemoryCache 檔案中呼叫該方法。

使用背景快取更新

使用 背景服務 如介面 IHostedService 來更新快取。 背景服務只能在項目準備好後重新計算並指派到快取。

檢視或下載範例程式碼 \(英文\) (如何下載)

快取基本概念

快取可藉由減少產生內容所需的工作,大幅改善應用程式的效能和延展性。 快取最適合不常變更 且 產生成本高昂的數據。 快取讓資料副本的返回速度比直接從來源取得快得多。 應用程式應該撰寫並測試, 永遠不會 相依於快取的數據。

ASP.NET Core 支援數個不同的快取。 最簡單的快取是以IMemoryCache為基礎。 IMemoryCache 表示儲存在網頁伺服器記憶體中的快取。 在伺服器農場(多部伺服器)上執行的應用程式應該在使用記憶體內部快取時確保會話持續連線。 黏滯會話可確保來自客戶端的後續要求全都發送到相同的伺服器。 例如,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 或更新版本。

Microsoft.Extensions.Caching.Memory/IMemoryCache(本文所述)建議使用而非System.Runtime.Caching/MemoryCache,因為它更能整合到 ASP.NET Core 中。 例如, IMemoryCache 以原生方式搭配 ASP.NET Core 依賴注入運作。

將程式代碼從 ASP.NET 4.x 移植到 ASP.NET Core 時,使用 System.Runtime.Caching/MemoryCache 作為相容性網橋。

快取準則

  • 程式碼應該始終有備用選項來擷取數據,而不是依賴快取值的可用性。
  • 快取使用了一種有限的資源:記憶體。 限制快取成長:

使用 IMemoryCache

Warning

從相依性插入使用共用記憶體快取,並呼叫SetSize、 Size或 SizeLimit 來限制快取大小,可能會導致應用程式失敗。 快取上設定大小限制時,所有專案都必須在新增時指定大小。 這可能會導致問題,因為開發人員可能無法完全控制使用共用快取的內容。 使用 SetSize、Size 或 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值會保留在快取中。

下列程式代碼會使用 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);
}

上述程式碼保證數據不會快取超過指定的時間。

GetOrCreate、 GetOrCreateAsync和 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

MemoryCache.Compact 試著依下列順序移除指定快取百分比:

  • 所有過期的物品。
  • 依優先順序排序的專案。 會先移除最低優先順序專案。
  • 最近使用最少的物件。
  • 具有最早絕對過期的項目。
  • 最早到期的項目。

具有優先權 NeverRemove 的釘選項目永遠不會移除。 下列程式代碼會移除快取項目並呼叫 Compact:

_cache.Remove(MyKey);

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

如需詳細資訊,請參閱 GitHub 上的 Compact 來源。

快取相依性

下列範例示範如果相依專案到期,如何讓快取專案過期。 一個 CancellationChangeToken 被加入至快取的專案。 當對Cancel呼叫CancellationTokenSource時,這兩個快取條目都會被移除。

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區塊內建立的快取項目將會繼承觸發條件和到期設定。

其他注意事項

  • 到期不會在背景發生。 沒有定時器會主動掃描快取以檢查是否有過期的項目。 快取(Get、Set、Remove)上的任何活動都可以觸發過期項目的背景掃描。 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 設定快取條目在被逐出後將執行的回呼函式。 在範例程式碼中,CancellationTokenSource.Dispose() 被呼叫來釋放 CancellationTokenSource 所使用的非受控資源。 不過, CancellationTokenSource 不會立即處置 ,因為它仍由快取專案使用。 CancellationToken 會傳遞至 MemoryCacheEntryOptions,以建立在特定時間後過期的快取條目。 因此,在移除或過期快取條目之前,不應呼叫 Dispose。 範例程式碼會呼叫 RegisterPostEvictionCallback 方法,以註冊一個回呼,在快取項目被逐出時叫用,並在該回呼中釋放 CancellationTokenSource。

  • 針對大部分的應用程式, IMemoryCache 會啟用 。 例如,呼叫AddMvc中的AddControllersWithViews、AddRazorPages、AddMvcCore().AddRazorViewEngine、Add{Service}和許多其他ConfigureServices方法,可以啟用IMemoryCache。 對於未呼叫上述Add{Service}方法之一的應用程式,可能需要在AddMemoryCache中呼叫ConfigureServices。

背景快取更新

使用 背景服務 ,例如 IHostedService 更新快取。 背景服務可以重新計算這些項目,並且只在它們準備就緒時才將其寫入快取。

其他資源