Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions docs/core/extensions/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,17 +217,20 @@ In the preceding C# code:

Consumers of this service are free to call `GetPhotosAsync` method, and handle photos accordingly. No `HttpClient` is required as the cache contains the photos.

The asynchronous signal is based on an encapsulated <xref:System.Threading.SemaphoreSlim> instance, within a generic-type constrained singleton. The `CacheSignal<T>` relies on an instance of `SemaphoreSlim`:

:::code source="snippets/caching/memory-worker/CacheSignal.cs":::

In the preceding C# code, the decorator pattern is used to wrap an instance of the `SemaphoreSlim`. Since the `CacheSignal<T>` is registered as a singleton, it can be used across all service lifetimes with any generic type &mdash; in this case, the `Photo`. It is responsible for signaling the seeding of the cache.

The `CacheWorker` is a subclass of <xref:Microsoft.Extensions.Hosting.BackgroundService>:

:::code source="snippets/caching/memory-worker/CacheWorker.cs":::

> [!IMPORTANT]
> You need to `override` <xref:Microsoft.Extensions.Hosting.BackgroundService.StartAsync%2A?displayProperty=nameWithType> and call `await _cacheSignal.WaitAsync()` in order to prevent a race condition between the starting of the `CacheWorker` and a call to `PhotoService.GetPhotosAsync`.

In the preceding C# code:

- The constructor requires an `ILogger`, `HttpClient`, `CacheSignal<Photo>`, and `IMemoryCache`.
- The defines an `_updateInterval` of three hours.
- The constructor requires an `ILogger`, `HttpClient`, and `IMemoryCache`.
- The `_updateInterval` is defined for three hours.
- The `ExecuteAsync` method:
- Loops while the app is running.
- Makes an HTTP request to `"https://jsonplaceholder.typicode.com/photos"`, and maps the response as an array of `Photo` objects.
Expand All @@ -236,11 +239,7 @@ In the preceding C# code:
- The call to <xref:System.Threading.Tasks.Task.Delay%2A?displayProperty=nameWithType> is awaited, given the update interval.
- After delaying for three hours, the cache is again updated.

The asynchronous signal is based on an encapsulated <xref:System.Threading.SemaphoreSlim> instance, within a generic-type constrained singleton. The `CacheSignal<T>` relies on an instance of `SemaphoreSlim`:

:::code source="snippets/caching/memory-worker/CacheSignal.cs":::

In the preceding C# code, the decorator pattern is used to wrap an instance of the `SemaphoreSlim`. Since the `CacheSignal<T>` is registered as a singleton, it can be used across all service lifetimes with any generic type &mdash; in this case, the `Photo`. It is responsible for signaling the seeding of the cache.
Consumers in the same process could ask the `IMemoryCache` for the photos, but the `CacheWorker` is responsible for updating the cache.

## Distributed caching

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public sealed class CacheWorker : BackgroundService
private readonly IMemoryCache _cache;
private readonly TimeSpan _updateInterval = TimeSpan.FromHours(3);

private bool _isCacheInitialized = false;

private const string Url = "https://jsonplaceholder.typicode.com/photos";

public CacheWorker(
Expand Down Expand Up @@ -52,7 +54,11 @@ await _httpClient.GetFromJsonAsync<Photo[]>(
}
finally
{
_cacheSignal.Release();
if (!_isCacheInitialized)
{
_cacheSignal.Release();
_isCacheInitialized = true;
}
}

try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
services.AddScoped<PhotoService>();
services.AddSingleton(typeof(CacheSignal<>));
})
.UseConsoleLifetime()
.Build();

await host.StartAsync();
Expand All @@ -26,11 +27,10 @@

PhotoService service =
scope.ServiceProvider.GetRequiredService<PhotoService>();

IAsyncEnumerable<Photo> photos = service.GetPhotosAsync(p => p.AlbumId == id);
await foreach (Photo photo in photos)

await foreach (Photo photo in service.GetPhotosAsync(p => p.AlbumId == id))
{
logger.LogInformation(photo.ToString());
logger.LogInformation("{PhotoDetails}", photo.ToString());
}

logger.LogInformation("");
Expand Down