Introduction
Here is a C# implementation of a Dijkstra counting Semaphore. This can also be used as a Reverse Sensing Semaphore as a thread can block until the “Count” reaches zero. Any thread(s) waiting on WaitForStarvation()
will be Pulsed by the first thread that “Released” the count to zero. In addition, this has many helper methods and overloads normally not available in other implementations, such as: current Count, Acquire with timeouts, AquireAll()
, ReleaseAll()
, WaitForStarvation()
, and “Try” versions of the Acquire
and Release
methods.
The Code:
using System;
using System.Threading;
namespace SyncPrimitives
{
public sealed class Semaphore
{
#region Fields
private int count;
private int maxCount;
private readonly object syncLock;
private readonly object starvationLock;
#endregion
#region Constructors
public Semaphore(int maxCount) : this(maxCount, maxCount)
{
}
public Semaphore(int initialCount, int maxCount)
{
if ( initialCount < 0 )
throw new
ArgumentOutOfRangeException("initialCount must be >= 0.");
if ( maxCount < 1 )
throw new ArgumentOutOfRangeException("maxCount must be >= 1.");
if ( initialCount > maxCount)
throw new
ArgumentOutOfRangeException("initialCount" +
" must be <= maxCount.");
count = initialCount;
this.maxCount = maxCount;
syncLock = new object();
starvationLock = new object();
}
#endregion
#region Properties
public int Count
{
get
{
lock(syncLock)
{
return count;
}
}
}
public int MaxCount
{
get { return maxCount; }
}
#endregion
#region Public Methods
public bool Acquire()
{
return Acquire(Timeout.Infinite);
}
public bool Acquire(int millisecondsTimeout)
{
lock(syncLock)
{
while ( count == 0 )
try
{
if (!Monitor.Wait(syncLock, millisecondsTimeout))
return false;
}
catch
{
Monitor.Pulse(syncLock);
throw;
}
count--;
if ( count == 0 )
lock(starvationLock) { Monitor.PulseAll(starvationLock); }
return true;
}
}
public bool AcquireAll()
{
return AcquireAll(Timeout.Infinite);
}
public bool AcquireAll(int millisecondsTimeout)
{
int slotsGot = 0;
int elapsedMS = 0;
DateTime start = DateTime.Now;
int timeout = millisecondsTimeout;
for (int i = 0; i < maxCount; i++)
{
try
{
if (! Acquire(timeout) )
{
if ( slotsGot > 0 )
Release(slotsGot);
return false;
}
else
{
elapsedMS = (int)((TimeSpan)
(DateTime.Now - start)).TotalMilliseconds;
timeout = millisecondsTimeout - elapsedMS;
if ( timeout < 0 )
timeout = 0;
slotsGot++;
}
}
catch
{
if ( slotsGot > 0 )
Release(slotsGot);
throw;
}
}
lock(starvationLock) { Monitor.PulseAll(starvationLock); }
return true;
}
public void Release()
{
Release(1);
}
public void Release(int releaseCount)
{
if ( releaseCount < 1 )
throw new
ArgumentOutOfRangeException("releaseCount must be >= 1.");
lock(syncLock)
{
if ( (count + releaseCount) > maxCount )
throw new
ArgumentOutOfRangeException("releaseCount" +
" would cause the semaphore's count to exceed maxCount.");
count += releaseCount;
Monitor.PulseAll(syncLock);
}
}
public bool TryRelease()
{
return TryRelease(1);
}
public bool TryRelease(int releaseCount)
{
if ( releaseCount <= 0 )
return false;
lock(syncLock)
{
if ( (count + releaseCount) > maxCount )
return false;
else
count += releaseCount;
Monitor.PulseAll(syncLock);
return true;
}
}
public void ReleaseAll()
{
lock(syncLock)
{
count = maxCount;
Monitor.PulseAll(syncLock);
}
}
public void WaitForStarvation()
{
lock(starvationLock)
{
if ( Interlocked.CompareExchange(ref count, 0, 0) != 0 )
Monitor.Wait(starvationLock);
}
}
#endregion
}
}
History
- Version 1.1 - 06/20/04 - Changed
AcquireAll(ms)
to respect user's milliseconds total.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.