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

Rick AndersonJohn LuoSteve Smith 撰寫

快取藉由減少產生內容所需的工作,可以大幅改善應用程式的效能和可擴縮性。 快取最適合不常變更產生成本高昂的資料。 快取可讓資料複本傳回的速度比來源快得多。 撰寫和測試應用程式時,應使其永遠不會相依於被快取的資料。

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 作為相容性橋接器。

快取指導方針

  • 程式碼應該一律有後援選項來擷取資料, 而不是相依於可用的快取值。
  • 快取會使用稀缺的資源、記憶體。 限制快取增長:
    • 將外部輸入插入快取中。 例如,不建議使用任意使用者提供的輸入做為快取索引鍵,因為輸入可能會耗用無法預測的記憶體數量。
    • 使用到期日來限制快取增長。
    • 使用 SetSize、Size 和 SizeLimit 來限制快取大小。 ASP.NET Core 執行時間不會根據記憶體壓力來限制快取大小。 是否限制快取的大小由開發人員決定。

使用 IMemoryCache

警告

使用來自相依性插入共用記憶體快取,並呼叫 SetSizeSizeSizeLimit 來限制快取大小,可能會導致應用程式失效。 對快取設定了大小限制時,所有項目在新增時都必須指定大小。 這可能會導致問題,因為開發人員可能無法完全控制使用共用快取的內容。 使用 SetSizeSizeSizeLimit 來限制快取時,請建立快取單一資料庫以執行快取。 如需詳細資訊和範例,請參閱使用 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 秒。 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));

在上述程式碼中,快取項目會設定為一天的相對到期時間。 快取項目會在一天以後從快取收回,即使它在這段到期期間內被存取也一樣。

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

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

GetOrCreateGetOrCreateAsyncGetCacheExtensions 類別中的擴充方法。 這些方法會擴充 IMemoryCache 的功能。

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

例如:

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

如果未設定 SizeLimit,快取就會漫無止境地增長。 ASP.NET Core 執行時間不會在系統記憶體不足時修剪快取。 應用程式必須架構為:

  • 限制快取增長。
  • 可用記憶體有限時呼叫 CompactRemove

下列程式碼會藉由相依性插入建立可存取的無單位固定大小 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);
}

在上述程式碼中,兩行醒目提示的行會達到設定快取項目大小相同的結果。 將呼叫鏈結至 new MemoryCacheOptions() 時,會提供 SetSize 以方便使用。

MemoryCache.Compact

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

  • 所有過期的項目。
  • 依優先順序排序的項目。 優先順序最低的項目會先被移除。
  • 最近使用最少的物件。
  • 絕對到期時間最早的項目。
  • 滑動到期時間最早的項目。

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

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

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

快取相依性

下列範例示範如果相依項目到期,如何讓快取項目到期。 CancellationChangeToken 會新增至快取項目。 在 CancellationTokenSource 上呼叫 Cancel 時,會收回這兩個快取項目:

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 範圍內建立的快取項目會繼承觸發程式和到期設定。

其他注意事項

  • 到期不會在背景中發生。 沒有會主動掃描快取中是否有項目到期的計時器。 快取 (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))
            .RegisterPostEvictionCallback((key, value, reason, state) =>
            {
                ((CancellationTokenSource)state).Dispose();
            }, cancellationTokenSource);
    
        _memoryCache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
    }
    
  • 使用回呼重新填入快取項目時:

    • 多個要求可以找到空白的快取索引鍵值,因為回呼尚未完成。
    • 這可能會導致數個執行緒重新填入快取項目。
  • 當一個快取項目用來建立另一個項目時,子系會複製父項目的到期權杖和以時間為基礎的到期設定。 手動移除或更新父系項目不會使子系過期。

  • 使用 PostEvictionCallbacks 來設定快取項目從快取收回之後會觸發的回呼。

  • 對於大部分的應用程式,IMemoryCache 會啟用。 例如,在 Program.cs 中呼叫 AddMvcAddControllersWithViewsAddRazorPagesAddMvcCore().AddRazorViewEngine 和許多其他Add{Service} 方法會啟用 IMemoryCache。 對於未呼叫上述其中一個 Add{Service} 方法的應用程式,可能需要在 Program.cs 中呼叫 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

警告

使用來自相依性插入共用記憶體快取,並呼叫 SetSizeSizeSizeLimit 來限制快取大小,可能會導致應用程式失效。 對快取設定了大小限制時,所有項目在新增時都必須指定大小。 這可能會導致問題,因為開發人員可能無法完全控制使用共用快取的內容。 使用 SetSizeSizeSizeLimit 來限制快取時,請建立快取單一資料庫以執行快取。 如需詳細資訊和範例,請參閱使用 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);
}

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

GetOrCreateGetOrCreateAsyncGetCacheExtensions 類別中的擴充方法。 這些方法會擴充 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 執行時間不會在系統記憶體不足時修剪快取。 應用程式必須架構為:

  • 限制快取增長。
  • 可用記憶體有限時呼叫 CompactRemove

下列程式碼會藉由相依性插入建立可存取的無單位固定大小 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");
    }
}

快取項目的大小可以透過 SizeSetSize 擴充方法來設定:

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 會新增至快取項目。 在 CancellationTokenSource 上呼叫 Cancel 時,會收回這兩個快取項目。

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);
    }
    
  • 使用回呼重新填入快取項目時:

    • 多個要求可以找到空白的快取索引鍵值,因為回呼尚未完成。
    • 這可能會導致數個執行緒重新填入快取項目。
  • 當一個快取項目用來建立另一個項目時,子系會複製父項目的到期權杖和以時間為基礎的到期設定。 手動移除或更新父系項目不會使子系過期。

  • 使用 PostEvictionCallbacks 來設定快取項目從快取收回之後會觸發的回呼。 在範例程式碼中,會呼叫 CancellationTokenSource.Dispose() 以釋放 CancellationTokenSource 所使用的非受控資源。 不過,CancellationTokenSource 不會立即被處理,因為它仍在被快取項目使用。 CancellationToken 會傳遞至 MemoryCacheEntryOptions,以建立在特定時間之後到期的快取項目。 因此,在快取項目被移除或過期之前,不應該呼叫 Dispose。 範例程式碼會呼叫 RegisterPostEvictionCallback 方法來註冊一個回呼,該回呼會在快取項目被收回時叫用,並在該回呼中處置 CancellationTokenSource

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

背景快取更新

使用背景服務 (例如 IHostedService) 來更新快取。 背景服務可以重新計算項目,然後只在備妥時將它們指派給快取。

其他資源