Table of Contents

Cache types

Tharga.Cache exposes four cache interfaces, each with a different expiration strategy. Inject the one that matches the lifetime your data needs — they share the same ICache operations (see Common operations) and differ only in when items expire.

Interface Expiration Lifetime Typical use
IEternalCache Never (until explicitly removed) Singleton Reference data that rarely changes
ITimeToLiveCache Fixed time after insertion (TTL) Singleton API responses, computed results
ITimeToIdleCache Reset on every access (TTI) Singleton Session-like data kept alive while in use
IScopeCache End of the DI scope Scoped Per-request memoization

IEternalCache

Data never expires unless explicitly dropped or invalidated. Registered as a singleton.

public class UserService(IEternalCache cache)
{
    public Task<User> GetUserAsync(string userId) =>
        cache.GetAsync<User>(userId, () => LoadUserAsync(userId));
}

ITimeToLiveCache

Data expires a fixed time after insertion (TTL). Registered as a singleton.

var data = await ttlCache.GetAsync<Product>(
    "product-123",
    () => LoadProductAsync(123),
    TimeSpan.FromMinutes(10));

ITimeToIdleCache

The expiration clock resets every time the item is accessed (TTI). Useful for session-like data that should stay cached while it is actively being used and expire once it goes quiet.

var session = await ttiCache.GetAsync<SessionData>(
    "session-abc",
    () => LoadSessionAsync("abc"),
    TimeSpan.FromMinutes(30));

IScopeCache

A scoped cache instance, cleared at the end of the DI scope (for example, per HTTP request). Data never expires within the scope, which makes it a clean way to memoize a value that may be requested several times while handling a single request.

var result = await scopeCache.GetAsync<RequestContext>(
    "current-context",
    () => BuildContextAsync());

Stale-while-revalidate

For the time-based caches, enabling StaleWhileRevalidate returns expired data immediately while fresh data is fetched in the background — eliminating the latency spike of a cache miss.

o.RegisterType<Product, IMemory>(t =>
{
    t.StaleWhileRevalidate = true;
    t.DefaultFreshSpan = TimeSpan.FromMinutes(5);
});

Use GetWithCallbackAsync to be notified when the fresh value arrives:

var (data, isFresh) = await cache.GetWithCallbackAsync<Product>(
    "product-123",
    () => LoadProductAsync(123),
    async freshData =>
    {
        // Called when the background refresh completes
        await NotifyClientsAsync(freshData);
    },
    TimeSpan.FromMinutes(5));

if (!isFresh)
{
    // data is stale; the callback will fire when fresh data is ready
}

Events

All cache types raise events for observing activity:

cache.DataSetEvent  += (sender, args) => Console.WriteLine($"Cached: {args.Key}");
cache.DataGetEvent  += (sender, args) => Console.WriteLine($"Retrieved: {args.Key}");
cache.DataDropEvent += (sender, args) => Console.WriteLine($"Removed: {args.Key}");