| | 1 | |
|
| | 2 | | using System; |
| | 3 | | using System.Collections.Concurrent; |
| | 4 | | using System.Threading.Tasks; |
| | 5 | |
|
| | 6 | | public class SingleFactoryCaller<T> where T : class |
| | 7 | | { |
| 31 | 8 | | private readonly ConcurrentDictionary<string, Lazy<Task<T>>> _pendingTasks |
| 31 | 9 | | = new ConcurrentDictionary<string, Lazy<Task<T>>>(); |
| | 10 | |
|
| | 11 | | public async Task<T> GetOrAddAsync(string key, Func<Task<T>> valueFactory) |
| | 12 | | { |
| 5298 | 13 | | if (string.IsNullOrEmpty(key)) |
| | 14 | | { |
| 2 | 15 | | throw new ArgumentException("Key cannot be null or empty.", nameof(key)); |
| | 16 | | } |
| | 17 | |
|
| 5296 | 18 | | var lazyValue = _pendingTasks.GetOrAdd(key, k => |
| 238 | 19 | | new Lazy<Task<T>>( |
| 238 | 20 | | async () => |
| 238 | 21 | | { |
| 238 | 22 | | try |
| 238 | 23 | | { |
| 238 | 24 | | return await valueFactory(); |
| 238 | 25 | | } |
| 238 | 26 | | finally |
| 238 | 27 | | { |
| 238 | 28 | | // Remove the entry only after the task completes (success or failure) |
| 238 | 29 | | // This ensures all concurrent waiters get the same result before cleanup |
| 238 | 30 | | _pendingTasks.TryRemove(key, out _); |
| 238 | 31 | | } |
| 128 | 32 | | } |
| 238 | 33 | | ) |
| 5296 | 34 | | ); |
| | 35 | |
|
| 5296 | 36 | | return await lazyValue.Value; |
| 5182 | 37 | | } |
| | 38 | | } |