In general what you are doing is polling a service to ask for a result or to ask if the result is ready. This is considered bad practice. Asking is always considered bad practice. Polling will always waste valuable resources for nothing.
Ancient polling can always be replaced by a modern event driven or asynchronous approach.
In your case, you simply have to convert your service ADHelpers
to a full asynchronous service.
The simplest would be if you are using an Active Directory API that supports asynchronous operations. From your attempt I can see, that you are trying to make the service asynchronous, therfore I assume, that the API doesn't support asynchronous calls. So, we have to make asynchrouns ourself.
The key is to add events to the ADHelpers
to notify a listener when the result is ready to consume. With the helpof TaskCompletionSource
we can convert the event driven pattern to an asynchronous pattern.
The following code shows how to implement an asynchronous service to replace polling. The new class also supports optional cancellation and an optional timeout:
ADHelpers.cs
class ADHelpers : IDisposable
{
public ADHelpers()
{
// Initilaize the timeout timer, but don't start it (set interval to Timeout.Infinite)
this.TmeoutTimer = new System.Threading.Timer(ExecuteTimeout, null, Timeout.Infinite , Timeout.Infinite);
}
// Method supports timeout which will be disabled by default (TimeSpan.Zero)
public async Task<Fullname> GetFullAdNameAsync(TimeSpan timeoutDuration = TimeSpan.Zero)
{
this.FullNameReady += CompleteAsyncOperation;
this.taskCompletionSource = new TaskCompletionSource<FullName>(TaskCreationOptions.LongRunning);
// Only enable the timeout timer when enabled via the parameters
if (timeoutDuration != TimeSpan.Zero)
{
this.TmeoutTimer.Change(TimeSpan.Zero, timeoutDuration);
}
GetFullAdName();
return this.taskCompletionSource.Task;
}
// Overload that additionally supports cancellation (and timeout)
public async Task<Fullname> GetFullAdNameAsync(CancellatioToken cancellationToken, TimeSpan timeoutDuration = TimeSpan.Zero)
{
this.CancellationToken = cancellationToken;
return await GetFullAdNameAsync(timeoutDuration);
}
private void GetFullAdName()
{
// The long running operation
FullName fullAdName = QueryAd();
// Complete the asynchronous operation
OnFullNameReady(fullAdName);
}
// To support cancellation, periodically invoke CancellationToken.ThrowIfCancellationRequested()
// as often as possible to give the operation chances to cancel
private FullName QueryAd()
{
try
{
this.CancellationToken?.ThrowIfCancellationRequested();
FullName result;
// Do something
this.CancellationToken?.ThrowIfCancellationRequested();
// Do something more
this.CancellationToken?.ThrowIfCancellationRequested();
return result;
}
finally
{
// Always stop the timer,
// even when exception was thrown or the task was cancelled.
this.TmeoutTimer.Change(Timeout.Infinite , Timeout.Infinite);
}
}
private void CompleteAsyncOperation(FullNameReadyEventArgs args)
{
// Return the result to the awaiting caller.
// Ends the asynchronous call by setting Task.Status to TaskStatus.RanToCompletion
this.taskCompletionSource.TrySetResult(args.FullAdName);
}
private void ExecuteTimeout(object stateInfo)
{
// Ends the asynchronous call by setting Task.Status to TaskStatus.Canceled
this.taskCompletionSource.TrySetCanceled();
}
#region Implementation of IDisposable
// Flag: Has Dispose already been called?
bool disposed = false;
// Instantiate a SafeHandle instance.
SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);
// Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
handle.Dispose();
// Free any other managed objects here.
this.TimeoutTimer?.Dispose();
}
disposed = true;
}
#endregion // IDisposable implementaion
private TaskCompletionSource<FullName> taskCompletionSource { get; set; }
private CancellationToken CancellationToken { get; set; }
private System.Threading.Timer TimeoutTimer { get; set; }
private event EventHandler<FullNameReadyEventArgs> FullNameReady;
protected virtual void OnFullNameReady(Fullname fullADName)
{
var eventArgs = new FullNameReadyEventArgs(fullADName);
this.FullNameReady?.Invoke(eventArgs);
}
}
Usage Example
// ADHelpers is now a IDisposable (because of the internal System.ThreadingTimer)
// Make sure that the instance is disposed properly. using-statement is recommended.
using (var adHelpers = new ADHelpers())
{
// Optional (if cancellation is required).
// Use 'cancellationTokenSource.Cancel()' to abort the asynchronous operation.
var cancellationTokenSource = new CancellationTokenSource();
// Optional: use a 10s timeout (the timeout is disabled by default)
var timeoutDuration = TimeSpan.FromSeconds(10);
FullName fullAdName = await adHelpers.GetFullAdNameAsync(cancellationTokenSource.Token, timeoutDuration);
}
Remarks
The code is not tested, but should compile. If not, please tell me so that I can fix the example.
ADHelpers
a custom class of yours you can modify? \$\endgroup\$SamAccountName
. Why do you ask? :-) \$\endgroup\$