Skip to content

Instantly share code, notes, and snippets.

@Porges
Last active January 15, 2019 08:47
Show Gist options
  • Save Porges/181e8b0d4489080f9eefad5e648c94f2 to your computer and use it in GitHub Desktop.
Save Porges/181e8b0d4489080f9eefad5e648c94f2 to your computer and use it in GitHub Desktop.
async Task Main()
{
var c = new AsyncCache<int, int>(async x => { await Task.Delay(x); return x; });
var s = Stopwatch.StartNew();
var x1 = Enumerable.Range(1, 1000);
var results =
await Task.WhenAll(x1.Select(async x =>
await Task.WhenAll(
// hit it in parallel
Task.Run(() => c.Get(x)),
Task.Run(() => c.Get(x)),
Task.Run(() => c.Get(x)))));
Console.WriteLine(s.Elapsed); // ~ 1s
}
class AsyncCache<TKey, TValue>
{
readonly ConcurrentDictionary<TKey, Task<TValue>> _dict =
new ConcurrentDictionary<TKey, Task<TValue>>();
readonly Func<TKey, Task<TValue>> _fetcher;
public AsyncCache(Func<TKey, Task<TValue>> fetcher)
=> _fetcher = fetcher;
public Task<TValue> Get(TKey key)
{
if (_dict.TryGetValue(key, out var task))
{
return task;
}
var tcs = new TaskCompletionSource<TValue>();
var result = _dict.GetOrAdd(key, tcs.Task);
if (result == tcs.Task)
{
// it's our task, we need to generate the value
return GenerateValue(key, tcs);
}
// else: someone else has populated the result
return result;
}
private async Task<TValue> GenerateValue(TKey key, TaskCompletionSource<TValue> tcs)
{
try
{
var value = await _fetcher(key);
tcs.SetResult(value); // share with other awaiters
return value;
}
catch (Exception ex)
{
tcs.SetException(ex); // also consider removing from the cache
throw;
}
finally
{
Debug.Assert(tcs.Task.IsCompleted);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment