Skip to content

Instantly share code, notes, and snippets.

@theodorzoulias
Last active August 22, 2022 09:38
Show Gist options
  • Save theodorzoulias/39d63727dd17e704ddd79c64e7916897 to your computer and use it in GitHub Desktop.
Save theodorzoulias/39d63727dd17e704ddd79c64e7916897 to your computer and use it in GitHub Desktop.
AsyncCollapseConcurrent<TResult> -- https://stackoverflow.com/a/73442817/11178549
/// <summary>
/// Represents an asynchronous operation that is invoked lazily on demand, can be
/// invoked multiple times, and is subject to a non-concurrent execution policy.
/// Concurrent observers receive the result of the same operation.
/// </summary>
public class AsyncCollapseConcurrent<TResult>
{
private readonly Func<Task<TResult>> _taskFactory;
private volatile Task<TResult> _task;
public AsyncCollapseConcurrent(Func<Task<TResult>> taskFactory)
{
ArgumentNullException.ThrowIfNull(taskFactory);
_taskFactory = taskFactory;
}
public Task<TResult> Task
{
get
{
Task<TResult> capturedTask = _task;
if (capturedTask is not null) return capturedTask;
Task<Task<TResult>> newTaskTask = new(_taskFactory);
Task<TResult> newTask = newTaskTask.Unwrap().ContinueWith(t =>
{
_task = null;
return t;
}, 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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment