Instantly share code, notes, and snippets.
Created
November 18, 2019 13:55
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save radleta/037797d8d6ac3c792dc83183cceb6785 to your computer and use it in GitHub Desktop.
Ensures exclusive execution of an async method.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Nito.AsyncEx; | |
using System; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace RichardAdleta | |
{ | |
/// <summary> | |
/// The cookie that holds an <see cref="Lock"/> with an <see cref="Completed"/>. The <see cref="Intialized"/> property | |
/// is set when <see cref="Completed"/> is already initailized. | |
/// </summary> | |
/// <typeparam name="TItem">The type of the item.</typeparam> | |
public class AsyncLockCookie<TResult> | |
{ | |
/// <summary> | |
/// The internal sync lock to coordinate creation of the <see cref="_task"/>. | |
/// </summary> | |
private readonly object _syncLock = new object(); | |
/// <summary> | |
/// The async method to use to create the item. | |
/// </summary> | |
private readonly Func<CancellationToken, Task<TResult>> _create; | |
/// <summary> | |
/// The item. | |
/// </summary> | |
public TaskCompletionSource<TResult> Completed { get; private set; } = new TaskCompletionSource<TResult>(); | |
/// <summary> | |
/// Determines whether or not the underlying task has been initialized. | |
/// </summary> | |
public bool Initialized { get; private set; } | |
/// <summary> | |
/// Intializes a new instance. | |
/// </summary> | |
/// <param name="create">The factory method to create the <see cref="TResult"/>.</param> | |
public AsyncLockCookie(Func<CancellationToken, Task<TResult>> create) | |
{ | |
_create = create ?? throw new ArgumentNullException(nameof(create)); | |
} | |
/// <summary> | |
/// Gets the Item using the exclusive lock to initialize it when its not initialized. | |
/// </summary> | |
/// <param name="cancellationToken">Cancellation the operation.</param> | |
/// <returns>The awaitable task of the item.</returns> | |
public Task<TResult> GetExclusiveAsync(System.Threading.CancellationToken cancellationToken) | |
{ | |
// when its already initialized then get it without the lock | |
if (Initialized) | |
{ | |
return Completed.Task.WaitAsync(cancellationToken); | |
} | |
// lock it | |
var creator = false; | |
lock (_syncLock) | |
{ | |
if (!Initialized) | |
{ | |
Initialized = true; | |
creator = true; | |
} | |
} | |
if (creator) | |
{ | |
return ExecuteAsync(cancellationToken); | |
} | |
return Completed.Task.WaitAsync(cancellationToken); | |
} | |
/// <summary> | |
/// Executes and sets the completion task. | |
/// </summary> | |
/// <param name="cancellationToken"></param> | |
/// <returns></returns> | |
private async Task<TResult> ExecuteAsync(System.Threading.CancellationToken cancellationToken) | |
{ | |
try | |
{ | |
var result = await _create(cancellationToken); | |
if (cancellationToken.IsCancellationRequested) | |
{ | |
Completed.TrySetCanceled(); | |
cancellationToken.ThrowIfCancellationRequested(); | |
return result; | |
} | |
Completed.TrySetResult(result); | |
return result; | |
} | |
catch (Exception ex) | |
{ | |
Completed.TrySetException(ex); | |
throw; | |
} | |
} | |
/// <summary> | |
/// Starts a separate task to execute the underlying task. | |
/// </summary> | |
/// <param name="cancellationToken">The cancellation token for the spawned task.</param> | |
public bool TryStart(System.Threading.CancellationToken cancellationToken) | |
{ | |
if (Initialized) | |
{ | |
return false; | |
} | |
var creator = false; | |
lock (_syncLock) | |
{ | |
if (!Initialized) | |
{ | |
Initialized = true; | |
creator = true; | |
} | |
} | |
if (creator) | |
{ | |
_ = Task.Run(() => ExecuteAsync(cancellationToken), cancellationToken); | |
} | |
return creator; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment