Skip to content

Instantly share code, notes, and snippets.

@i3arnon
Created October 1, 2016 12:36
Show Gist options
  • Save i3arnon/203432dedd7d086cd70694629b41e1a2 to your computer and use it in GitHub Desktop.
Save i3arnon/203432dedd7d086cd70694629b41e1a2 to your computer and use it in GitHub Desktop.
public class Syncer<TKey, TResult>
{
private readonly ConcurrentDictionary<TKey, TaskCompletionSource<TResult>> _taskCompletionSources = new ConcurrentDictionary<TKey, TaskCompletionSource<TResult>>();
public Task<TResult> RunAsync(TKey key, Func<Task<TResult>> func)
{
var newTaskCompletionSource = new TaskCompletionSource<TResult>();
var existingTaskCompletionSource = _taskCompletionSources.GetOrAdd(key, newTaskCompletionSource);
if (newTaskCompletionSource == existingTaskCompletionSource)
{
var task = func();
task.ContinueWith(
_ =>
{
// Copy over result to newly created TCS
newTaskCompletionSource.TryCompleteFromCompletedTask(_);
// Avoid unobserved task exception
newTaskCompletionSource.Task.ContinueWith(antecedent => antecedent.Exception);
// Clear tcs when done
TaskCompletionSource<TResult> dummy;
_taskCompletionSources.TryRemove(key, out dummy);
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
return task;
}
return existingTaskCompletionSource.Task;
}
}
public static class TaskCompletionSourceExtensions
{
public static void TryCompleteFromCompletedTask<TResult>(this TaskCompletionSource<TResult> @this, Task<TResult> task)
{
if (task.IsFaulted)
{
@this.TrySetException(task.Exception.InnerExceptions);
}
else if (task.IsCanceled)
{
@this.TrySetCanceled();
}
else
{
@this.TrySetResult(task.GetAwaiter().GetResult());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment