Skip to content

Instantly share code, notes, and snippets.

@theodorzoulias
Last active November 14, 2023 08:45
Show Gist options
  • Save theodorzoulias/3060659a206e1543e120efe903eeeedb to your computer and use it in GitHub Desktop.
Save theodorzoulias/3060659a206e1543e120efe903eeeedb to your computer and use it in GitHub Desktop.
GetOrAddAsync for ConcurrentDictionary<TKey, ValueTask<TValue>> -- https://stackoverflow.com/questions/54117652/concurrentdictionary-getoradd-async
/// <summary>
/// Returns an existing task from the concurrent dictionary, or adds a new task
/// using the specified asynchronous factory method. Concurrent invocations for
/// the same key are prevented, unless the task is removed before the completion
/// of the delegate. Failed tasks are evicted from the concurrent dictionary.
/// </summary>
public static ValueTask<TValue> GetOrAddAsync<TKey, TValue>(
this ConcurrentDictionary<TKey, ValueTask<TValue>> source, TKey key,
Func<TKey, Task<TValue>> valueFactory)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(valueFactory);
ValueTask<TValue> currentTask;
if (source.TryGetValue(key, out currentTask)) return currentTask;
Task<Task<TValue>> newTaskTask = new(() => valueFactory(key));
ValueTask<TValue> newTask = default;
newTask = new(newTaskTask.Unwrap().ContinueWith(task =>
{
if (task.IsCompletedSuccessfully)
source.TryUpdate(key, new(task.Result), newTask);
else
source.TryRemove(KeyValuePair.Create(key, newTask));
return task;
}, default, TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default).Unwrap());
currentTask = source.GetOrAdd(key, newTask);
if (currentTask == newTask) newTaskTask.RunSynchronously(TaskScheduler.Default);
return currentTask;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment