using System; using System.Threading.Tasks; namespace System.Collections.Concurrent { public static class ConcurrentDictionaryExtensions { /// /// Provides an alternative to that disposes values that implement . /// /// /// /// /// /// /// public static TValue GetOrAddWithDispose( this ConcurrentDictionary dictionary, TKey key, Func valueFactory) where TValue : IDisposable { while (true) { if (dictionary.TryGetValue(key, out var value)) { // Try to get the value return value; } /// Try to add the value value = valueFactory(key); if (dictionary.TryAdd(key, value)) { // Won the race, so return the instance return value; } // Lost the race, dispose the created object value.Dispose(); } } /// /// Provides an alternative to specifically for asynchronous values. The factory method will only run once. /// /// /// /// /// /// /// public static async Task GetOrAddAsync( this ConcurrentDictionary> dictionary, TKey key, Func> valueFactory) { while (true) { if (dictionary.TryGetValue(key, out var task)) { return await task; } // This is the task that we'll return to all waiters. We'll complete it when the factory is complete var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); if (dictionary.TryAdd(key, tcs.Task)) { try { var value = await valueFactory(key); tcs.TrySetResult(value); return await tcs.Task; } catch (Exception ex) { // Make sure all waiters see the exception tcs.SetException(ex); // We remove the entry if the factory failed so it's not a permanent failure // and future gets can retry (this could be a pluggable policy) dictionary.TryRemove(key, out _); throw; } } } } } }