共用方式為


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

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

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

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

Web 伺服器陣列中的非黏性工作階段需要 分散式快取 ,以避免快取一致性問題。 對於某些應用程式,分散式快取可以支援比記憶體內部快取更高的向外延展。 使用分散式快取會將快取記憶體卸除至外部進程。

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

System.Runtime.Caching/MemoryCache

System.Runtime.Caching / MemoryCacheNuGet 套件) 可以搭配使用:

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

上述程式代碼保證數據不會快取超過絕對時間。

GetOrCreateGetOrCreateAsyncGet 是類別中的 CacheExtensions 擴充方法。 這些方法會擴充 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);
}

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

MemoryCache.Compact

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

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

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

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

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

(No changes necessary, so the improved translation remains the same as the original.) 快取相依性

下列範例示範如果相依專案到期,如何讓快取專案過期。 一個 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)上的任何活動都可以觸發過期項目的背景掃描。 CancellationTokenSourceCancelAfter)上的一個定時器也會移除項目,並觸發掃描已過期的項目。 下列範例會針對已註冊的權杖使用 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中的AddControllersWithViewsAddRazorPagesAddMvcCore().AddRazorViewEngineAdd{Service}和許多其他Program.cs方法,可以啟用IMemoryCache。 對於未呼叫上述Add{Service}其中一個方法的應用程式,可能需要在AddMemoryCache中呼叫Program.cs

背景快取更新

使用 背景服務 ,例如 IHostedService 更新快取。 背景服務可以重新計算條目,然後僅在它們準備好時再指派給快取。

其他資源

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

快取基本概念

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

ASP.NET Core 支援數個不同的快取。 最簡單的快取是以IMemoryCache為基礎。 IMemoryCache 表示儲存在網頁伺服器記憶體中的快取。 在伺服器農場(多部伺服器)上執行的應用程式應該在使用記憶體內部快取時確保會話持續連線。 黏滯會話可確保來自客戶端的後續要求全都發送到相同的伺服器。 例如,Azure Web 應用程式會使用 應用程式要求路由 (ARR) 將所有後續要求路由傳送至相同的伺服器。

Web 伺服器陣列中的非黏性工作階段需要 分散式快取 ,以避免快取一致性問題。 對於某些應用程式,分散式快取可以支援比記憶體內部快取更高的向外延展。 使用分散式快取會將快取記憶體卸除至外部進程。

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

System.Runtime.Caching/MemoryCache

System.Runtime.Caching / MemoryCacheNuGet 套件) 可以搭配使用:

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

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

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 執行時不會在系統記憶體不足時清理快取。 應用程式必須架構為:

  • 限制快取成長。
  • 呼叫 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 來源

(No changes necessary, so the improved translation remains the same as the original.) 快取相依性

下列範例示範如果相依專案到期,如何讓快取專案過期。 一個 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區塊內建立的快取項目將會繼承觸發條件和到期設定。

其他注意事項

  • 到期不會在背景發生。 沒有定時器會主動掃描快取以檢查是否有過期的項目。 快取(GetSetRemove)上的任何活動都可以觸發過期項目的背景掃描。 CancellationTokenSourceCancelAfter)上的一個定時器也會移除項目,並觸發掃描已過期的項目。 下列範例會針對已註冊的權杖使用 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中的AddControllersWithViewsAddRazorPagesAddMvcCore().AddRazorViewEngineAdd{Service}和許多其他ConfigureServices方法,可以啟用IMemoryCache。 對於未呼叫上述Add{Service}方法之一的應用程式,可能需要在AddMemoryCache中呼叫ConfigureServices

背景快取更新

使用 背景服務 ,例如 IHostedService 更新快取。 背景服務可以重新計算條目,然後僅在它們準備好時再指派給快取。

其他資源