TT (Tom Tempelaere) said:
[...]
Plus the millisecondsTimeout is deceiving, because the max time could be
( maxSlots * millisecondsTimeout )
to acquire all slots.
Here's my try to implement Acquire( int nrSlots ). Warning: it doesn't look
very efficient though
.
Another issue is that lock could take some time and that should counted too
to check for timeout. Modifications are inside the code for _Acquire_N &
RegularTimeoutTracker.
public sealed class Semaphore
{
private int currentCount;
private readonly int maxCount;
private readonly object acquireLock = new object();
private readonly object starvationLock = new object();
public Semaphore( int currentCount, int maxCount )
{
Debug.Assert (
maxCount >= 1 &&
currentCount >= 0 &&
currentCount <= maxCount
);
this.currentCount = currentCount;
this.maxCount = maxCount;
}
public Semaphore( int maxCount )
: this( maxCount, maxCount )
{}
public bool Acquire()
{
return _Acquire_N( 1, Timeout.Infinite );
}
public bool Acquire( int timeoutMs )
{
return _Acquire_N( 1, timeoutMs );
}
public bool AcquireAll()
{
return _Acquire_N( maxCount, Timeout.Infinite );
}
public bool AcquireAll( int overallTimeoutMs )
{
return _Acquire_N( maxCount, overallTimeoutMs );
}
public bool Acquire_N( int requestedCount )
{
return _Acquire_N( requestedCount, Timeout.Infinite );
}
public bool Acquire_N( int requestedCount, int overallTimeoutMs )
{
return _Acquire_N( requestedCount, overallTimeoutMs );
}
private bool _Acquire_N( int requestedCount, int overallTimeoutMs )
{
Debug.Assert (
( requestedCount > 0 ) &&
( overallTimeoutMs > 0 || ( overallTimeoutMs == Timeout.Infinite ) )
);
IAcquireTimeoutTracker acqTimeoutTracker = ( overallTimeoutMs > 0 ) ?
(IAcquireTimeoutTracker) new RegularTimeoutTracker( overallTimeoutMs ) :
new InfiniteTimeoutTracker();
int acquiredCount = 0;
if( Monitor.TryEnter( acquireLock, acqTimeoutTracker.RemainingWaitTime ) )
try
{
do
{
while( currentCount == 0 )
if( acqTimeoutTracker.HasElapsed ||
!Monitor.Wait( acquireLock, acqTimeoutTracker.RemainingWaitTime ) )
throw new TimeoutException();
int targetCount = Math.Min( (requestedCount - acquiredCount),
currentCount );
acquiredCount += targetCount;
currentCount -= targetCount;
if( currentCount == 0 )
lock( starvationLock )
Monitor.PulseAll( starvationLock );
}
while( acquiredCount != requestedCount );
return true;
}
catch( Exception e )
{
if( acquiredCount > 0 )
Release( acquiredCount );
Monitor.Pulse( acquireLock );
if( !(e is TimeoutException) )
throw;
}
finally
{
Monitor.Exit( acquireLock );
}
return false;
}
private class TimeoutException : Exception
{
public override string Message {
get {
return "Waiting timeout";
}
}
}
internal interface IAcquireTimeoutTracker
{
bool HasElapsed {
get;
}
int RemainingWaitTime {
get;
}
}
internal class InfiniteTimeoutTracker : IAcquireTimeoutTracker
{
public bool HasElapsed {
get {
return false;
}
}
public int RemainingWaitTime {
get {
return Timeout.Infinite;
}
}
}
internal class RegularTimeoutTracker : IAcquireTimeoutTracker
{
DateTime deadline;
TimeSpan timeout;
public RegularTimeoutTracker( int timeoutMs )
{
this.deadline = DateTime.Now + TimeSpan.FromMilliseconds( timeoutMs );
this.timeout = deadline - DateTime.Now;
}
public bool HasElapsed {
get {
return (timeout = deadline - DateTime.Now) <= TimeSpan.Zero;
}
}
public int RemainingWaitTime {
get {
Debug.Assert (
timeout.Milliseconds > 0
);
return timeout.Milliseconds;
}
}
}
public bool Release( int count )
{
return true;
}
}
Looks better. Only starvation isn't checked for timeout.
Tom T.