6

I have the following class to manage access to a resource:

class Sync : IDisposable
{
    private static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(20);

    private Sync()
    {
    }

    public static async Task<Sync> Acquire()
    {
        await Semaphore.WaitAsync();
        return new Sync();
    }

    public void Dispose()
    {
        Semaphore.Release();
    }
}

Usage:

using (await Sync.Acquire())
{
    // use a resource here
}

Now it allows not more than 20 shared usages.

How to modify this class to allow not more than N shared usages per unit of time (for example, not more than 20 per second)?

alexey
  • 8,360
  • 14
  • 70
  • 102
  • Could you `Acquire` then use the resource in a different thread? If so - you could have the main thread wait the fixed amount of time, then release the semaphore. Just a thought – drew_w Jun 19 '14 at 16:25
  • Maybe this can give an idea: http://stackoverflow.com/questions/18771524/limit-the-number-of-tasks-in-task-factory-start-by-second – L.B Jun 19 '14 at 16:29

2 Answers2

3

"20 per second" is completely different than "20 at a time". I recommend that you leave the thread synchronization behind and use higher-level abstractions capable of working more naturally with time as a concept.

In particular, Reactive Extensions has a number of different throttling operators.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • `Observable.Throttle` would lose requests. You could try `Observable.CombineLatest(requestObservable, Observable.Timer(TimeSpan.FromMiliseconds(100)), (x, _) => x).Subscribe(DoSomething)` – Aron Jun 20 '14 at 05:35
1

Here's a basic reimplementation which calls Semaphore.Release either when the specified time period has elapsed, or (optionally - see code comments in Dispose()) when the Sync instance is disposed.

class Sync : IDisposable
{
    private static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(20);

    // 0 : semaphore needs to be released.
    // 1 : semaphore already released.
    private int State = 0;

    private Sync()
    {
    }

    // Renamed to conform to Microsoft's guidelines.
    public static async Task<Sync> AcquireAsync(TimeSpan releaseAfter)
    {
        var sync = new Sync();

        await Semaphore.WaitAsync().ConfigureAwait(false);

        try
        {
            return sync;
        }
        finally
        {
            // Fire-and-forget, not awaited.
            sync.DelayedRelease(releaseAfter);
        }
    }

    private async void DelayedRelease(TimeSpan releaseAfter)
    {
        await Task.Delay(releaseAfter).ConfigureAwait(false);

        this.ReleaseOnce();
    }

    private void ReleaseOnce()
    {
        // Ensure that we call Semaphore.Release() at most
        // once during the lifetime of this instance -
        // either via DelayedRelease, or via Dispose.
        if (Interlocked.Exchange(ref this.State, 1) == 0)
        {
            Semaphore.Release();
        }
    }

    public void Dispose()
    {
        // Uncomment if you want the ability to
        // release the semaphore via Dispose
        // thus bypassing the throttling.

        //this.ReleaseOnce();
    }
}
Kirill Shlenskiy
  • 9,367
  • 27
  • 39