Skip to content

Instantly share code, notes, and snippets.

@aarondandy
Created July 9, 2020 22:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aarondandy/45782fa9475ad4d70dd357f84c3efff5 to your computer and use it in GitHub Desktop.
Save aarondandy/45782fa9475ad4d70dd357f84c3efff5 to your computer and use it in GitHub Desktop.
async blocking memory cache
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace CacheTesting
{
class Program
{
static async Task Main(string[] args)
{
var cache = new MemoryCache(new MemoryCacheOptions());
var asyncCache = new AsyncMemoryCache(cache);
var values = await Task.WhenAll(Enumerable.Range(0, 128).Select(async n =>
{
var key = (n / 10).ToString();
var delay = 100 + (n % 10);
// bad things
//return await cache.GetOrCreateAsync(key, async e =>
//{
// // pretend this is an in memory thing that is expensive to construct
// await Task.Delay(delay).ConfigureAwait(false);
// return RandomNext(100);
//}).ConfigureAwait(false);
// good things
return await asyncCache.GetOrCreateBlockingAsync(key, async e =>
{
// pretend this is an in memory thing that is expensive to construct
await Task.Delay(delay).ConfigureAwait(false);
return RandomNext(100);
}).ConfigureAwait(false);
})).ConfigureAwait(false);
for (var i = 0; i < values.Length; i++)
{
Console.WriteLine($"{i}:\t{values[i]}");
}
Console.ReadKey();
}
static Random rand = new Random();
static int RandomNext(int n)
{
lock (rand)
{
return rand.Next(n);
}
}
}
// Idea from https://michaelscodingspot.com/cache-implementations-in-csharp-net/
class AsyncMemoryCache : IMemoryCache
{
public AsyncMemoryCache(IMemoryCache memoryCache)
{
_cache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache));
_locks = new ConcurrentDictionary<object, SemaphoreSlim>();
}
private readonly IMemoryCache _cache;
private readonly ConcurrentDictionary<object, SemaphoreSlim> _locks;
public Task<TItem> GetOrCreateBlockingAsync<TItem>(object key, Func<ICacheEntry, Task<TItem>> factory, CancellationToken cancellationToken = default) =>
_cache.TryGetValue(key, out TItem result)
? Task.FromResult(result)
: GetOrCreateLocked(key, (e, _) => factory(e), cancellationToken);
public Task<TItem> GetOrCreateBlockingAsync<TItem>(object key, Func<ICacheEntry, CancellationToken, Task<TItem>> factory, CancellationToken cancellationToken = default) =>
_cache.TryGetValue(key, out TItem result)
? Task.FromResult(result)
: GetOrCreateLocked(key, factory, cancellationToken);
private async Task<TItem> GetOrCreateLocked<TItem>(object key, Func<ICacheEntry, CancellationToken, Task<TItem>> factory, CancellationToken cancellationToken)
{
TItem result;
var semaphore = _locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (!_cache.TryGetValue(key, out result))
{
result = await factory(_cache.CreateEntry(key), cancellationToken).ConfigureAwait(false);
_cache.Set(key, result);
}
}
finally
{
_locks.TryRemove(key, out _);
semaphore.Release();
// okay but who will dispose of the semaphore?
}
return result;
}
public bool TryGetValue(object key, out object value)
{
return _cache.TryGetValue(key, out value);
}
public ICacheEntry CreateEntry(object key)
{
return _cache.CreateEntry(key);
}
public void Remove(object key)
{
_cache.Remove(key);
}
public void Dispose()
{
_cache.Dispose();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment