CSharp · Console
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
public class Program
{
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);
try
{
Print($"**Factory invoked ({executions}), Concurrency: {concurrency}");
Thread.Sleep(300); // Simulate a blocking operation.
await Task.Delay(200); // Simulate an I/O-bound operation.
if (executions <= 2) throw new ApplicationException($"Oops! ({executions})");
return 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();
// Measure the duration of getting the task.
var task = asyncExpiringLazy.Task;
var duration1 = stopwatch.ElapsedMilliseconds; stopwatch.Restart();
// Measure the duration of awaiting the task.
try { await task; } catch { }
var duration2 = stopwatch.ElapsedMilliseconds;
string timeInfo = $" (blocked for {duration1:#,0} msec, awaited for {duration2:#,0} msec)";
try
{
var result = await task;
Print($"--Worker #{n} received value: {result}{timeInfo}");
}
catch (Exception ex)
{
Print($"--Worker #{n} failed: {ex.Message}{timeInfo}");
}
})).ToArray();
await Task.WhenAll(workers);
Print($"Finished");
}
/// <summary>
/// Represents the result of an asynchronous operation that is invoked lazily
/// on demand, with the option to retry it as many times as needed until it
/// succeeds, while enforcing a non-overlapping execution policy.
/// </summary>
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
{
get
{
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)
{
_taskFactory = null; // No longer needed (let it get recycled)
return task;
}
// Discard the stored _task, to trigger a retry later.
var original = Interlocked.Exchange(ref _task, null);
Debug.Assert(ReferenceEquals(original, newTask));
return task;
}, default, TaskContinuationOptions.DenyChildAttach |
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default).Unwrap();
capturedTask = Interlocked
.CompareExchange(ref _task, newTask, null) ?? newTask;
if (ReferenceEquals(capturedTask, newTask))
newTaskTask.RunSynchronously(TaskScheduler.Default);
return capturedTask;
}
}
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}");
}
}