I have multiple distinct processes that need to access external resources that are rate limited. The processes are all async in nature and run in different applications. In times past I would just use SemaphoreSlim this design doesn't allow for that.
I've found several samples that seem to be half complete, or cut and paste into their code. This was derived from an existing post, but heavily modified to encompass the additional methods and to honor the cancellation token.
Any feedback would be appreciated.
public sealed class SemaphoreAsync : IDisposable
{
Semaphore _semaphore;
private SemaphoreAsync(Semaphore sem) => _semaphore = sem;
public SemaphoreAsync(int initialCount, int maximumCount) => _semaphore = new Semaphore(initialCount, maximumCount);
public SemaphoreAsync(int initialCount, int maximumCount, string name) => _semaphore = new Semaphore(initialCount, maximumCount, name);
public SemaphoreAsync(int initialCount, int maximumCount, string name, out bool createdNew, SemaphoreSecurity semaphoreSecurity) => _semaphore = new Semaphore(initialCount, maximumCount, name, out createdNew, semaphoreSecurity);
public static SemaphoreAsync OpenExisting(string name)
{
return new SemaphoreAsync(Semaphore.OpenExisting(name));
}
public static SemaphoreAsync OpenExisting(string name, SemaphoreRights rights)
{
return new SemaphoreAsync(Semaphore.OpenExisting(name, rights));
}
public static bool TryOpenExisting(string name, out SemaphoreAsync result)
{
if (Semaphore.TryOpenExisting(name, out Semaphore semaphore))
{
result = new SemaphoreAsync(semaphore);
return true;
}
result = null;
return false;
}
public static bool TryOpenExisting(string name, SemaphoreRights rights, out SemaphoreAsync result)
{
if (Semaphore.TryOpenExisting(name, rights, out Semaphore semaphore))
{
result = new SemaphoreAsync(semaphore);
return true;
}
result = null;
return false;
}
public async Task<bool> WaitOne(TimeSpan timeout, CancellationToken ct)
{
DateTime start = DateTime.UtcNow;
while (!_semaphore.WaitOne(0))
{
ct.ThrowIfCancellationRequested();
if (DateTime.UtcNow < start.Add(timeout))
return false;
await Task.Delay(100, ct);
}
return true;
}
public async Task<bool> WaitOne(int millisecondsTimeout, CancellationToken ct)
{
DateTime start = DateTime.UtcNow;
while (!_semaphore.WaitOne(0))
{
ct.ThrowIfCancellationRequested();
if (millisecondsTimeout > 0)
{
if (DateTime.UtcNow < start.AddMilliseconds(millisecondsTimeout))
return false;
}
await Task.Delay(100, ct);
}
return true;
}
public async Task<bool> WaitOne(CancellationToken ct)
{
while (!_semaphore.WaitOne(0))
{
ct.ThrowIfCancellationRequested();
await Task.Delay(100, ct);
}
return true;
}
public SemaphoreSecurity GetAccessControl()
{
return _semaphore.GetAccessControl();
}
public int Release()
{
return _semaphore.Release();
}
public int Release(int releaseCount)
{
return _semaphore.Release(releaseCount);
}
public void SetAccessControl(SemaphoreSecurity semaphoreSecurity)
{
_semaphore.SetAccessControl(semaphoreSecurity);
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
if (_semaphore != null)
{
_semaphore.Dispose();
_semaphore = null;
}
}
disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
}
#endregion
}
ConcurrentDictionary
to storeSemaphoreSlim
s.SemaphoreSlim
already supports async operations. \$\endgroup\$