liveBook · Manning

Caching

As explained in C# Concurrency: Asynchronous and multithreaded programming

A cache is a storage layer that retains previously obtained values so they can be returned more quickly on later requests.

A cache retains previously obtained values so later requests can avoid repeating an expensive operation. A typical application cache stores HTTP results in a Dictionary keyed by the request, while CPU cache memory stores recently accessed data closer to processor cores. section 5.3 section 7.2

Protect a cache according to the work it performs:

  • Lock only the dictionary access before and after an await; holding a lock across await is invalid because continuation code may run on another thread and can cause deadlocks. section 5.3
  • Expect concurrent misses when the lock is released during the HTTP call: multiple callers can fetch the same value, and later completions overwrite earlier ones. section 5.3
  • Use synchronization rather than homemade flags for shared state; compiler reordering, stale CPU cache values, and per-core caches can make unsynchronized observations incorrect. section 7.2
  • Treat volatile cautiously: it does not guarantee access to the latest value and may introduce additional problems. section 7.2
Listing 5.10 Releasing the lock while awaiting
private Dictionary<string,string> _cache = new();
private object _cacheLock = new();
public async Task<string> GetResult(string query)
{
   lock(_cacheLock)                                         #1
   {
       if(_cache.TryGetValue(query, out var cacheResult)
          return cacheResult;
   }
   var http = new HttpClient();
   var result = await http.GetStringAsync(                  #2
      "https://example.com?"+query);                        #2
   lock(_cacheLock)                                         #3
   {
      _cache[query] = result;
   }
   return result;
}

Shows a C# cache implementation that checks the dictionary under a lock, performs the asynchronous HTTP request outside it, and locks again when storing the result. section 5.3

Concurrent asynchronous lookup

Two callers request the same missing query. The first caller observes a miss and releases the lock before awaiting the HTTP request. The second caller also observes a miss and starts another request. When the calls finish, each updates the cache; the later update replaces the earlier value. section 5.3

How C# Concurrency: Asynchronous and multithreaded programming covers it

How deep it goes
Explains lock placement, asynchronous races, compiler reordering, and CPU cache memory, but does not develop eviction or expiration policies.
Examples
Uses a C# Dictionary cache around an asynchronous HttpClient request and contrasts whole-method locking with split lock boundaries.
What it assumes
Assumes familiarity with C# methods and shared state while explaining asynchronous continuation threads, locks, compiler reordering, and CPU cache memory.
What you can do after
Apply it via C# Dictionary, lock, and async/await while recognizing duplicate misses and unsafe unsynchronized memory access.
How it connects
Ties caching to locks, async/await, HTTP calls, dictionaries, multithreading, memory ordering, and volatile semantics.

Also explained in

Compare how 38 books explain caching →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C# Concurrency: Asynchronous and multithreaded programming. The section links point to the parts of the book it draws on.