Buforowanie na platformie .NET
W tym artykule poznasz różne mechanizmy buforowania. Buforowanie jest działaniem przechowywania danych w warstwie pośredniej, dzięki czemu kolejne pobieranie danych jest szybsze. Koncepcyjnie buforowanie jest strategią optymalizacji wydajności i zagadnieniami dotyczącymi projektowania. Buforowanie może znacząco poprawić wydajność aplikacji, wprowadzając często zmieniające (lub kosztowne) dane bardziej łatwo dostępne. W tym artykule przedstawiono dwa podstawowe typy buforowania i przedstawiono przykładowy kod źródłowy dla obu typów:
Ważne
Istnieją dwie MemoryCache
klasy w obrębie platformy .NET— jedna w System.Runtime.Caching
przestrzeni nazw, a druga w Microsoft.Extensions.Caching
przestrzeni nazw:
Chociaż ten artykuł koncentruje się na buforowaniu, nie zawiera System.Runtime.Caching
pakietu NuGet. Wszystkie odwołania do MemoryCache
programu znajdują się w Microsoft.Extensions.Caching
przestrzeni nazw.
Microsoft.Extensions.*
Wszystkie pakiety są gotowe do wstrzykiwania zależności (DI), zarówno IMemoryCache interfejsy, jak i IDistributedCache mogą być używane jako usługi.
Buforowanie w pamięci
W tej sekcji dowiesz się więcej o pliku Microsoft.Extensions.Buforowanie. Pakiet pamięci. Bieżąca implementacja elementu IMemoryCache to otoka interfejsu ConcurrentDictionary<TKey,TValue>API, która udostępnia bogaty w funkcje interfejs API. Wpisy w pamięci podręcznej są reprezentowane przez element ICacheEntryi mogą być dowolnymi object
elementami . Rozwiązanie pamięci podręcznej w pamięci jest doskonałe dla aplikacji uruchamianych na jednym serwerze, gdzie wszystkie buforowane dane wynajmują pamięć w procesie aplikacji.
Napiwek
W przypadku scenariuszy buforowania z wieloma serwerami należy rozważyć podejście buforowania rozproszonego jako alternatywę dla buforowania w pamięci.
Interfejs API buforowania w pamięci
Użytkownik pamięci podręcznej ma kontrolę zarówno nad przesuwanymi, jak i bezwzględnymi wygasaniami:
- ICacheEntry.AbsoluteExpiration
- ICacheEntry.AbsoluteExpirationRelativeToNow
- ICacheEntry.SlidingExpiration
Ustawienie wygaśnięcia spowoduje eksmitowanie wpisów w pamięci podręcznej, jeśli nie są one dostępne w ramach przydziału czasu wygaśnięcia. Użytkownicy mają dodatkowe opcje kontrolowania wpisów pamięci podręcznej za pośrednictwem elementu MemoryCacheEntryOptions. Każda ICacheEntry z nich jest sparowana, z MemoryCacheEntryOptions którą uwidacznia funkcję eksmisji wygasania z ustawieniami IChangeTokenpriorytetu za pomocą CacheItemPrioritypolecenia i kontrolując element ICacheEntry.Size. Rozważ następujące metody rozszerzenia:
- MemoryCacheEntryExtensions.AddExpirationToken
- MemoryCacheEntryExtensions.RegisterPostEvictionCallback
- MemoryCacheEntryExtensions.SetSize
- MemoryCacheEntryExtensions.SetPriority
Przykład pamięci podręcznej w pamięci
Aby użyć implementacji domyślnej IMemoryCache , wywołaj metodę AddMemoryCache rozszerzenia, aby zarejestrować wszystkie wymagane usługi w usłudze DI. W poniższym przykładzie kodu host ogólny jest używany do uwidaczniania funkcji DI:
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
using IHost host = builder.Build();
W zależności od obciążenia platformy .NET można uzyskać dostęp do różnych IMemoryCache
elementów, takich jak iniekcja konstruktora. W tym przykładzie używasz IServiceProvider
wystąpienia w metodzie host
i wywołaj metodę rozszerzenia ogólnego GetRequiredService<T>(IServiceProvider) :
IMemoryCache cache =
host.Services.GetRequiredService<IMemoryCache>();
Dzięki zarejestrowaniu usług buforowania w pamięci i rozwiązaniu za pomocą di możesz rozpocząć buforowanie. Ten przykład iteruje litery alfabetu angielskiego "A" do "Z". Typ record AlphabetLetter
przechowuje odwołanie do litery i generuje komunikat.
file record AlphabetLetter(char Letter)
{
internal string Message =>
$"The '{Letter}' character is the {Letter - 64} letter in the English alphabet.";
}
Napiwek
Modyfikator file
dostępu jest używany w typieAlphabetLetter
, ponieważ jest on zdefiniowany w pliku Program.cs i uzyskiwany tylko do niego. Aby uzyskać więcej informacji, zobacz plik (odwołanie w C#). Aby wyświetlić pełny kod źródłowy, zobacz sekcję Program.cs .
Przykład zawiera funkcję pomocnika, która iteruje litery alfabetu:
static async ValueTask IterateAlphabetAsync(
Func<char, Task> asyncFunc)
{
for (char letter = 'A'; letter <= 'Z'; ++letter)
{
await asyncFunc(letter);
}
Console.WriteLine();
}
W poprzednim kodzie języka C#:
- Element
Func<char, Task> asyncFunc
jest oczekiwany dla każdej iteracji, przekazując bieżącyletter
element . - Po przetworzeniu wszystkich liter do konsoli jest zapisywany pusty wiersz.
Aby dodać elementy do pamięci podręcznej, wywołaj jedną z Create
interfejsów API lub Set
:
var addLettersToCacheTask = IterateAlphabetAsync(letter =>
{
MemoryCacheEntryOptions options = new()
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
};
_ = options.RegisterPostEvictionCallback(OnPostEviction);
AlphabetLetter alphabetLetter =
cache.Set(
letter, new AlphabetLetter(letter), options);
Console.WriteLine($"{alphabetLetter.Letter} was cached.");
return Task.Delay(
TimeSpan.FromMilliseconds(MillisecondsDelayAfterAdd));
});
await addLettersToCacheTask;
W poprzednim kodzie języka C#:
- Delegaty zmiennej
addLettersToCacheTask
doIterateAlphabetAsync
i są oczekiwane. - Argumentem
Func<char, Task> asyncFunc
jest lambda. - Element
MemoryCacheEntryOptions
jest tworzone przy użyciu bezwzględnego wygaśnięcia względem teraz. - Wywołanie zwrotne po eksmisji jest zarejestrowane.
- Obiekt
AlphabetLetter
jest tworzone i przekazywany razem Set z elementamiletter
ioptions
. - Litera jest zapisywana w konsoli jako buforowana.
- Task.Delay Na koniec zostanie zwrócony element .
Dla każdej litery alfabetu wpis pamięci podręcznej jest zapisywany z wygaśnięciem i ogłasza wywołanie zwrotne eksmisji.
Wywołanie zwrotne po eksmisji zapisuje szczegóły wartości eksmitowanej w konsoli:
static void OnPostEviction(
object key, object? letter, EvictionReason reason, object? state)
{
if (letter is AlphabetLetter alphabetLetter)
{
Console.WriteLine($"{alphabetLetter.Letter} was evicted for {reason}.");
}
};
Teraz, gdy pamięć podręczna zostanie wypełniona, zostanie wyświetlone kolejne wywołanie IterateAlphabetAsync
metody , ale tym razem wywołasz metodę IMemoryCache.TryGetValue:
var readLettersFromCacheTask = IterateAlphabetAsync(letter =>
{
if (cache.TryGetValue(letter, out object? value) &&
value is AlphabetLetter alphabetLetter)
{
Console.WriteLine($"{letter} is still in cache. {alphabetLetter.Message}");
}
return Task.CompletedTask;
});
await readLettersFromCacheTask;
Jeśli element cache
zawiera letter
klucz, a value
element jest wystąpieniem AlphabetLetter
zapisywanym w konsoli programu . letter
Gdy klucz nie znajduje się w pamięci podręcznej, został wykluczony i wywołano wywołanie zwrotne po eksmisji.
Dodatkowe metody rozszerzenia
Zestaw IMemoryCache
zawiera wiele metod rozszerzeń opartych na wygodzie, w tym asynchroniczną GetOrCreateAsync
metodę :
- CacheExtensions.Get
- CacheExtensions.GetOrCreate
- CacheExtensions.GetOrCreateAsync
- CacheExtensions.Set
- CacheExtensions.TryGetValue
Zebranie wszystkich elementów
Cały przykładowy kod źródłowy aplikacji jest programem najwyższego poziomu i wymaga dwóch pakietów NuGet:
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
using IHost host = builder.Build();
IMemoryCache cache =
host.Services.GetRequiredService<IMemoryCache>();
const int MillisecondsDelayAfterAdd = 50;
const int MillisecondsAbsoluteExpiration = 750;
static void OnPostEviction(
object key, object? letter, EvictionReason reason, object? state)
{
if (letter is AlphabetLetter alphabetLetter)
{
Console.WriteLine($"{alphabetLetter.Letter} was evicted for {reason}.");
}
};
static async ValueTask IterateAlphabetAsync(
Func<char, Task> asyncFunc)
{
for (char letter = 'A'; letter <= 'Z'; ++letter)
{
await asyncFunc(letter);
}
Console.WriteLine();
}
var addLettersToCacheTask = IterateAlphabetAsync(letter =>
{
MemoryCacheEntryOptions options = new()
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
};
_ = options.RegisterPostEvictionCallback(OnPostEviction);
AlphabetLetter alphabetLetter =
cache.Set(
letter, new AlphabetLetter(letter), options);
Console.WriteLine($"{alphabetLetter.Letter} was cached.");
return Task.Delay(
TimeSpan.FromMilliseconds(MillisecondsDelayAfterAdd));
});
await addLettersToCacheTask;
var readLettersFromCacheTask = IterateAlphabetAsync(letter =>
{
if (cache.TryGetValue(letter, out object? value) &&
value is AlphabetLetter alphabetLetter)
{
Console.WriteLine($"{letter} is still in cache. {alphabetLetter.Message}");
}
return Task.CompletedTask;
});
await readLettersFromCacheTask;
await host.RunAsync();
file record AlphabetLetter(char Letter)
{
internal string Message =>
$"The '{Letter}' character is the {Letter - 64} letter in the English alphabet.";
}
Możesz dostosować MillisecondsDelayAfterAdd
wartości i MillisecondsAbsoluteExpiration
, aby obserwować zmiany zachowania w wygaśnięciu i eksmisji buforowanych wpisów. Poniżej przedstawiono przykładowe dane wyjściowe z uruchamiania tego kodu. Ze względu na niedeterministyczny charakter zdarzeń platformy .NET dane wyjściowe mogą się różnić.
A was cached.
B was cached.
C was cached.
D was cached.
E was cached.
F was cached.
G was cached.
H was cached.
I was cached.
J was cached.
K was cached.
L was cached.
M was cached.
N was cached.
O was cached.
P was cached.
Q was cached.
R was cached.
S was cached.
T was cached.
U was cached.
V was cached.
W was cached.
X was cached.
Y was cached.
Z was cached.
A was evicted for Expired.
C was evicted for Expired.
B was evicted for Expired.
E was evicted for Expired.
D was evicted for Expired.
F was evicted for Expired.
H was evicted for Expired.
K was evicted for Expired.
L was evicted for Expired.
J was evicted for Expired.
G was evicted for Expired.
M was evicted for Expired.
N was evicted for Expired.
I was evicted for Expired.
P was evicted for Expired.
R was evicted for Expired.
O was evicted for Expired.
Q was evicted for Expired.
S is still in cache. The 'S' character is the 19 letter in the English alphabet.
T is still in cache. The 'T' character is the 20 letter in the English alphabet.
U is still in cache. The 'U' character is the 21 letter in the English alphabet.
V is still in cache. The 'V' character is the 22 letter in the English alphabet.
W is still in cache. The 'W' character is the 23 letter in the English alphabet.
X is still in cache. The 'X' character is the 24 letter in the English alphabet.
Y is still in cache. The 'Y' character is the 25 letter in the English alphabet.
Z is still in cache. The 'Z' character is the 26 letter in the English alphabet.
Ponieważ bezwzględne wygaśnięcie (MemoryCacheEntryOptions.AbsoluteExpirationRelativeToNow) jest ustawione, wszystkie buforowane elementy zostaną ostatecznie wykluczone.
Buforowanie usługi roboczej
Jedną z typowych strategii buforowania danych jest aktualizowanie pamięci podręcznej niezależnie od usług danych zużywających dane. Szablon usługi procesu roboczego jest doskonałym przykładem, ponieważ BackgroundService działa niezależnie (lub w tle) z innego kodu aplikacji. Po uruchomieniu aplikacji, która hostuje implementację IHostedService, odpowiednia implementacja (w tym przypadku BackgroundService
proces roboczy lub "proces roboczy") rozpoczyna działanie w tym samym procesie. Te hostowane usługi są rejestrowane w usłudze DI jako singletons za pośrednictwem AddHostedService<THostedService>(IServiceCollection) metody rozszerzenia. Inne usługi można zarejestrować w usłudze DI z dowolnym okresem istnienia usługi.
Ważne
Okres istnienia usługi jest bardzo ważny, aby zrozumieć. Po wywołaniu AddMemoryCache metody rejestrowania wszystkich usług buforowania w pamięci usługi są rejestrowane jako pojedyncze.
Scenariusz usługi fotograficznej
Wyobraź sobie, że tworzysz usługę zdjęć, która korzysta z interfejsu API innej firmy dostępnego za pośrednictwem protokołu HTTP. Te dane fotograficzne nie zmieniają się bardzo często, ale jest ich wiele. Każde zdjęcie jest reprezentowane przez proste record
:
namespace CachingExamples.Memory;
public readonly record struct Photo(
int AlbumId,
int Id,
string Title,
string Url,
string ThumbnailUrl);
W poniższym przykładzie zobaczysz kilka usług zarejestrowanych w usłudze DI. Każda usługa ma jedną odpowiedzialność.
using CachingExamples.Memory;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
builder.Services.AddHttpClient<CacheWorker>();
builder.Services.AddHostedService<CacheWorker>();
builder.Services.AddScoped<PhotoService>();
builder.Services.AddSingleton(typeof(CacheSignal<>));
using IHost host = builder.Build();
await host.StartAsync();
W poprzednim kodzie języka C#:
- Host ogólny jest tworzony z wartościami domyślnymi.
- Usługi buforowania w pamięci są rejestrowane w usłudze AddMemoryCache.
- Wystąpienie
HttpClient
jest rejestrowane dla klasy zaCacheWorker
pomocą AddHttpClient<TClient>(IServiceCollection)polecenia . - Klasa jest zarejestrowana
CacheWorker
w pliku AddHostedService<THostedService>(IServiceCollection). - Klasa jest zarejestrowana
PhotoService
w pliku AddScoped<TService>(IServiceCollection). - Klasa jest zarejestrowana
CacheSignal<T>
w pliku AddSingleton. - Wystąpienie obiektu
host
z konstruktora zostało utworzone i zostało uruchomione asynchronicznie.
Jest PhotoService
odpowiedzialny za pobieranie zdjęć spełniających podane kryteria (lub filter
):
using Microsoft.Extensions.Caching.Memory;
namespace CachingExamples.Memory;
public sealed class PhotoService(
IMemoryCache cache,
CacheSignal<Photo> cacheSignal,
ILogger<PhotoService> logger)
{
public async IAsyncEnumerable<Photo> GetPhotosAsync(Func<Photo, bool>? filter = default)
{
try
{
await cacheSignal.WaitAsync();
Photo[] photos =
(await cache.GetOrCreateAsync(
"Photos", _ =>
{
logger.LogWarning("This should never happen!");
return Task.FromResult(Array.Empty<Photo>());
}))!;
// If no filter is provided, use a pass-thru.
filter ??= _ => true;
foreach (Photo photo in photos)
{
if (!default(Photo).Equals(photo) && filter(photo))
{
yield return photo;
}
}
}
finally
{
cacheSignal.Release();
}
}
}
W poprzednim kodzie języka C#:
- Konstruktor wymaga
IMemoryCache
,CacheSignal<Photo>
iILogger
. - Metoda
GetPhotosAsync
:Func<Photo, bool> filter
Definiuje parametr i zwraca wartośćIAsyncEnumerable<Photo>
.- Wywołuje i czeka na
_cacheSignal.WaitAsync()
wydanie, dzięki czemu pamięć podręczna zostanie wypełniona przed uzyskaniem dostępu do pamięci podręcznej. - Wywołuje
_cache.GetOrCreateAsync()
metodę , asynchronicznie uzyskując wszystkie zdjęcia w pamięci podręcznej. factory
Argument rejestruje ostrzeżenie i zwraca pustą tablicę zdjęć — nigdy nie powinno się to zdarzyć.- Każde zdjęcie w pamięci podręcznej jest iterowane, filtrowane i zmaterializowane za pomocą elementu
yield return
. - Na koniec sygnał pamięci podręcznej zostanie zresetowany.
Konsumenci tej usługi mogą wywoływać GetPhotosAsync
metodę i odpowiednio obsługiwać zdjęcia. Nie HttpClient
jest wymagane, ponieważ pamięć podręczna zawiera zdjęcia.
Sygnał asynchroniczny jest oparty na hermetyzowanym SemaphoreSlim wystąpieniu w ramach pojedynczego pojedynczego typu ogólnego. Obiekt CacheSignal<T>
opiera się na wystąpieniu klasy SemaphoreSlim
:
namespace CachingExamples.Memory;
public sealed class CacheSignal<T>
{
private readonly SemaphoreSlim _semaphore = new(1, 1);
/// <summary>
/// Exposes a <see cref="Task"/> that represents the asynchronous wait operation.
/// When signaled (consumer calls <see cref="Release"/>), the
/// <see cref="Task.Status"/> is set as <see cref="TaskStatus.RanToCompletion"/>.
/// </summary>
public Task WaitAsync() => _semaphore.WaitAsync();
/// <summary>
/// Exposes the ability to signal the release of the <see cref="WaitAsync"/>'s operation.
/// Callers who were waiting, will be able to continue.
/// </summary>
public void Release() => _semaphore.Release();
}
W poprzednim kodzie języka C# wzorzec dekoratora służy do zawijania wystąpienia klasy SemaphoreSlim
. Ponieważ element CacheSignal<T>
jest zarejestrowany jako pojedynczy, może być używany we wszystkich okresach istnienia usługi z dowolnym typem ogólnym — w tym przypadku Photo
. Jest on odpowiedzialny za sygnalizowanie rozmieszczania pamięci podręcznej.
Jest CacheWorker
to podklasa :BackgroundService
using System.Net.Http.Json;
using Microsoft.Extensions.Caching.Memory;
namespace CachingExamples.Memory;
public sealed class CacheWorker(
ILogger<CacheWorker> logger,
HttpClient httpClient,
CacheSignal<Photo> cacheSignal,
IMemoryCache cache) : BackgroundService
{
private readonly TimeSpan _updateInterval = TimeSpan.FromHours(3);
private bool _isCacheInitialized = false;
private const string Url = "https://jsonplaceholder.typicode.com/photos";
public override async Task StartAsync(CancellationToken cancellationToken)
{
await cacheSignal.WaitAsync();
await base.StartAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
logger.LogInformation("Updating cache.");
try
{
Photo[]? photos =
await httpClient.GetFromJsonAsync<Photo[]>(
Url, stoppingToken);
if (photos is { Length: > 0 })
{
cache.Set("Photos", photos);
logger.LogInformation(
"Cache updated with {Count:#,#} photos.", photos.Length);
}
else
{
logger.LogWarning(
"Unable to fetch photos to update cache.");
}
}
finally
{
if (!_isCacheInitialized)
{
cacheSignal.Release();
_isCacheInitialized = true;
}
}
try
{
logger.LogInformation(
"Will attempt to update the cache in {Hours} hours from now.",
_updateInterval.Hours);
await Task.Delay(_updateInterval, stoppingToken);
}
catch (OperationCanceledException)
{
logger.LogWarning("Cancellation acknowledged: shutting down.");
break;
}
}
}
}
W poprzednim kodzie języka C#:
- Konstruktor wymaga
ILogger
,HttpClient
iIMemoryCache
. - Element
_updateInterval
jest definiowany przez trzy godziny. - Metoda
ExecuteAsync
:- Pętle podczas działania aplikacji.
- Wysyła żądanie HTTP do
"https://jsonplaceholder.typicode.com/photos"
elementu i mapuje odpowiedź jako tablicęPhoto
obiektów. - Tablica zdjęć jest umieszczana w
IMemoryCache
pod kluczem"Photos"
. - Nazywa
_cacheSignal.Release()
się, uwalniając wszystkich konsumentów, którzy czekali na sygnał. - Wywołanie metody Task.Delay jest oczekiwane, biorąc pod uwagę interwał aktualizacji.
- Po opóźnieniu przez trzy godziny pamięć podręczna zostanie ponownie zaktualizowana.
Konsumenci w tym samym procesie mogą poprosić IMemoryCache
o zdjęcia, ale CacheWorker
jest odpowiedzialny za aktualizowanie pamięci podręcznej.
Rozproszone buforowanie
W niektórych scenariuszach wymagana jest rozproszona pamięć podręczna — na przykład wiele serwerów aplikacji. Rozproszona pamięć podręczna obsługuje wyższe skalowanie w poziomie niż podejście buforowania w pamięci. Użycie rozproszonej pamięci podręcznej odciąża pamięć podręczną do procesu zewnętrznego, ale wymaga dodatkowych operacji we/wy sieci i wprowadza nieco większe opóźnienie (nawet jeśli nominalne).
Rozproszone abstrakcji buforowania są częścią Microsoft.Extensions.Caching.Memory
pakietu NuGet i istnieje nawet AddDistributedMemoryCache
metoda rozszerzenia.
Uwaga
Element AddDistributedMemoryCache powinien być używany tylko w scenariuszach tworzenia i/lub testowania i nie jest opłacalną implementacją produkcyjną.
Weź pod uwagę dowolną z dostępnych implementacji z IDistributedCache
następujących pakietów:
Microsoft.Extensions.Caching.SqlServer
Microsoft.Extensions.Caching.StackExchangeRedis
NCache.Microsoft.Extensions.Caching.OpenSource
Interfejs API rozproszonego buforowania
Interfejsy API buforowania rozproszonego są nieco bardziej pierwotne niż ich odpowiedniki interfejsu API buforowania w pamięci. Pary klucz-wartość są nieco bardziej podstawowe. Klucze buforowania w pamięci są oparte na object
obiekcie , natomiast klucze rozproszone to string
. W przypadku buforowania w pamięci wartość może być dowolną silnie typizowaną ogólną wartością, natomiast wartości w buforowaniu rozproszonym są utrwalane jako byte[]
. Nie oznacza to, że różne implementacje nie uwidaczniają silnie typizowane wartości ogólne, ale byłoby to szczegóły implementacji.
Tworzenie wartości
Aby utworzyć wartości w rozproszonej pamięci podręcznej, wywołaj jeden z zestawów interfejsów API:
Korzystając z rekordu AlphabetLetter
z przykładu w pamięci podręcznej, można serializować obiekt w formacie JSON, a następnie kodować string
jako :byte[]
DistributedCacheEntryOptions options = new()
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
};
AlphabetLetter alphabetLetter = new(letter);
string json = JsonSerializer.Serialize(alphabetLetter);
byte[] bytes = Encoding.UTF8.GetBytes(json);
await cache.SetAsync(letter.ToString(), bytes, options);
Podobnie jak buforowanie w pamięci, wpisy pamięci podręcznej mogą mieć opcje ułatwiające dostosowanie ich istnienia w pamięci podręcznej — w tym przypadku DistributedCacheEntryOptions.
Tworzenie metod rozszerzenia
Istnieje kilka wygodnych metod rozszerzenia do tworzenia wartości, które pomagają uniknąć kodowania string
reprezentacji obiektów w obiekcie byte[]
:
Odczytywanie wartości
Aby odczytać wartości z rozproszonej pamięci podręcznej, wywołaj jeden z interfejsów API pobierania:
AlphabetLetter? alphabetLetter = null;
byte[]? bytes = await cache.GetAsync(letter.ToString());
if (bytes is { Length: > 0 })
{
string json = Encoding.UTF8.GetString(bytes);
alphabetLetter = JsonSerializer.Deserialize<AlphabetLetter>(json);
}
Gdy wpis pamięci podręcznej zostanie odczytany z pamięci podręcznej, możesz uzyskać reprezentację zakodowaną string
w formacie UTF8 z pliku byte[]
Odczytywanie metod rozszerzenia
Istnieje kilka wygodnych metod rozszerzeń do odczytywania wartości, które pomagają uniknąć dekodowania byte[]
w string
reprezentacjach obiektów:
Aktualizowanie wartości
Nie ma możliwości zaktualizowania wartości w rozproszonej pamięci podręcznej za pomocą pojedynczego wywołania interfejsu API, zamiast tego wartości mogą mieć ich przesuwane wygasania resetowania przy użyciu jednego z interfejsów API odświeżania:
Jeśli wartość rzeczywista musi zostać zaktualizowana, musisz usunąć wartość, a następnie ponownie ją dodać.
Usuwanie wartości
Aby usunąć wartości w rozproszonej pamięci podręcznej, wywołaj jeden z usuniętych interfejsów API:
Napiwek
Chociaż istnieją synchroniczne wersje wyżej wymienionych interfejsów API, należy wziąć pod uwagę fakt, że implementacje rozproszonych pamięci podręcznych są zależne od operacji we/wy sieci. Z tego powodu preferowane jest częściej niż nie używanie asynchronicznych interfejsów API.