Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.Caching.Memory
{
/// <summary>
/// Thread safe version of GetOrCreate(Async) using Interlocked
/// </summary>
public static class InterlockedCacheExtensions
{
private class Lock
{
public long Value;
public Exception Exception;
}
private static readonly ConcurrentDictionary<ValueTuple<object, IMemoryCache>, Lock> Interlocks =
new ConcurrentDictionary<ValueTuple<object, IMemoryCache>, Lock>();
public static TItem GetOrCreateInterlocked<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, TItem> factoryMethod)
{
return cache.GetOrCreateInterlockedAsync(key, (a) => Task.FromResult(factoryMethod(a))).Result;
}
public static async Task<TItem> GetOrCreateInterlockedAsync<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, Task<TItem>> factoryMethod)
{
if (!cache.TryGetValue(key, out object result))
{
var interlock = Interlocks.GetOrAdd((key, cache), (_) => new Lock());
if (Interlocked.CompareExchange(ref interlock.Value, 0x1, 0x0) == 0x0)
{
try
{
if (!cache.TryGetValue(key, out result))
{
var entry = cache.CreateEntry(key);
entry.SetValue(result = await factoryMethod(entry)).Dispose();
}
}
catch (Exception e)
{
interlock.Exception = e;
}
finally
{
Interlocks.TryRemove((key, cache), out Lock oldAsync);
Interlocked.Exchange(ref interlock.Value, 0x0);
}
}
else
{
SpinWait.SpinUntil(() => cache.TryGetValue(key, out result) || Interlocked.Read(ref interlock.Value) != 0x1);
if (interlock.Exception != null)
throw interlock.Exception;
}
}
return (TItem)result;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment