I am trying to throttle SemaphoreSlim (i.e. allow initialization with a negative initialCount). A scenario could be that you're hitting an API and may notice degradation due to a server overload, so you'd want to start throttling requests to, say, 10 concurrent requests. However, you would be keeping count of the concurrent requests and know that at this time there are 25 concurrent requests, but you can't start a SemaphoreSlim(-15, 10).
I tried making an implementation that allows this, but I'm not 100% sure whether or not this is thread-safe and if it could be optimized (e.g. doing without the locks).
public class SemaphoreSlimThrottle : SemaphoreSlim
{
private volatile int _throttleCount;
private readonly object _lock = new object();
public SemaphoreSlimThrottle(int initialCount)
: base(initialCount)
{
}
public SemaphoreSlimThrottle(int initialCount, int maxCount)
: base(Math.Max(0, initialCount), maxCount)
{
_throttleCount = Math.Min(0, initialCount);
}
public new int CurrentCount => _throttleCount + base.CurrentCount;
public new int Release()
{
if (_throttleCount < 0)
{
lock (_lock)
{
if (_throttleCount < 0)
{
_throttleCount++;
return _throttleCount - 1;
}
}
}
return base.Release();
}
public new int Release(int releaseCount)
{
if (releaseCount < 1)
{
base.Release(releaseCount); // throws exception
}
if (releaseCount + _throttleCount <= 0)
{
lock (_lock)
{
if (releaseCount + _throttleCount <= 0)
{
_throttleCount += releaseCount;
return _throttleCount - releaseCount;
}
}
}
if (_throttleCount < 0)
{
lock (_lock)
{
if (_throttleCount < 0)
{
int output = CurrentCount;
base.Release(releaseCount + _throttleCount);
_throttleCount = 0;
return output;
}
}
}
return base.Release(releaseCount);
}
}
I've packaged this as a NuGet package with source available on GitHub.