Showing posts with label using. Show all posts
Showing posts with label using. Show all posts

Friday, March 18, 2016

How to Release a Semaphore with a Using Block

I love that .NET has so many useful utilities available in the base framework. I often use the SemaphoreSlim, and I love that it supports async await. However, I don't like always having to create a try finally block around every use call to ensure that the release method gets called.

Below is a simple little extension method that will allow you to place the result of the Semaphore wait into a using block, thus ensuring that the dispose will always release the lock.

SemaphoreSlim Extensions

public static class SemaphoreSlimExtensions
{
    public static async Task<IDisposable> UseWaitAsync(
        this SemaphoreSlim semaphore, 
        CancellationToken cancelToken = default(CancellationToken))
    {
        await semaphore.WaitAsync(cancelToken).ConfigureAwait(false);
        return new ReleaseWrapper(semaphore);
    }
 
    private class ReleaseWrapper : IDisposable
    {
        private readonly SemaphoreSlim _semaphore;
 
        private bool _isDisposed;
 
        public ReleaseWrapper(SemaphoreSlim semaphore)
        {
            _semaphore = semaphore;
        }
 
        public void Dispose()
        {
            if (_isDisposed)
                return;
 
            _semaphore.Release();
            _isDisposed = true;
        }
    }
}

Sunday, October 4, 2015

Throttles - Delay vs Semaphore

A while back I had talked about how to await an interval with a simple Throttle class that I had made. This is a very easy way to control how often you start an operation, but it does not ensure a limit to how many operations are happening at a given time.

Problem: You want to only make 5 requests per second to a remote service.

With the throttle class set to only allow one new call to start every 200 milliseconds you will only be able to start 5 new requests per second. However, if those calls take longer than two seconds to complete, then during the next second you will have 10 requests in flight at the same time.

Solution: Set your throttle to 200 millisecond and add a semaphore with a count of 5.

You can solve this problem by combining your throttle with a semaphore. This will ensure that you only start a new operation at your schedule frequency, and also that you never have more than a predetermined number in flight at the same time.

Please note that only using a semaphore would not solve the problem because if, in the same example, the calls take sub 200 milliseconds to complete then more than 5 requests will start each second.

Below is a set of helper classes (and tests) to help extend the throttle class to support this functionality within a using block.

Interfaces

public interface IUsableSemaphore : IDisposable
{
    Task<IUsableSemaphoreWrapper> WaitAsync();
}
 
public interface IUsableSemaphoreWrapper : IDisposable
{
    TimeSpan Elapsed { get; }
}
 
public interface IThrottle
{
    Task WaitAsync();
}

Real Time Web Analytics