using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
public static async Task Main()
int executionsCounter = 0;
int concurrencyCounter = 0;
AsyncLazy<int> asyncExpiringLazy = new(async () =>
var executions = Interlocked.Increment(ref executionsCounter);
var concurrency = Interlocked.Increment(ref concurrencyCounter);
Print($"**Factory invoked ({executions}), Concurrency: {concurrency}");
if (executions <= 2) throw new ApplicationException($"Oops! ({executions})");
finally { Interlocked.Decrement(ref concurrencyCounter); }
}, retryOnFailure: true);
Task[] workers = Enumerable.Range(1, 12).Select(n => Task.Run(async () =>
await Task.Delay((n - 1) * 200);
Print($"Worker #{n} requesting value");
var stopwatch = Stopwatch.StartNew();
var task = asyncExpiringLazy.Task;
var duration1 = stopwatch.ElapsedMilliseconds; stopwatch.Restart();
try { await task; } catch { }
var duration2 = stopwatch.ElapsedMilliseconds;
string timeInfo = $" (blocked for {duration1:#,0} msec, awaited for {duration2:#,0} msec)";
Print($"--Worker #{n} received value: {result}{timeInfo}");
Print($"--Worker #{n} failed: {ex.Message}{timeInfo}");
await Task.WhenAll(workers);
public class AsyncLazy<TResult>
private Func<Task<TResult>> _taskFactory;
private readonly bool _retryOnFailure;
private Task<TResult> _task;
public AsyncLazy(Func<Task<TResult>> taskFactory, bool retryOnFailure = false)
ArgumentNullException.ThrowIfNull(taskFactory);
_taskFactory = taskFactory;
_retryOnFailure = retryOnFailure;
public Task<TResult> Task
var capturedTask = Volatile.Read(ref _task);
if (capturedTask is not null) return capturedTask;
var newTaskTask = new Task<Task<TResult>>(_taskFactory);
Task<TResult> newTask = null;
newTask = newTaskTask.Unwrap().ContinueWith(task =>
if (task.IsCompletedSuccessfully || !_retryOnFailure)
var original = Interlocked.Exchange(ref _task, null);
Debug.Assert(ReferenceEquals(original, newTask));
}, default, TaskContinuationOptions.DenyChildAttach |
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default).Unwrap();
capturedTask = Interlocked
.CompareExchange(ref _task, newTask, null) ?? newTask;
if (ReferenceEquals(capturedTask, newTask))
newTaskTask.RunSynchronously(TaskScheduler.Default);
public TaskAwaiter<TResult> GetAwaiter() => Task.GetAwaiter();
public ConfiguredTaskAwaitable<TResult> ConfigureAwait(
bool continueOnCapturedContext)
=> Task.ConfigureAwait(continueOnCapturedContext);
private static void Print(object value)
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} {($"[{Thread.CurrentThread.ManagedThreadId}]"),4} > {value}");