Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Timed semaphore

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)?

like image 218
alexey Avatar asked Oct 20 '22 06:10

alexey


2 Answers

"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.

like image 187
Stephen Cleary Avatar answered Oct 23 '22 03:10

Stephen Cleary


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();
    }
}
like image 39
Kirill Shlenskiy Avatar answered Oct 23 '22 01:10

Kirill Shlenskiy