0

Currently I use this class (got it from here):

public static class IntSynchronizer
{
    private static readonly ConcurrentDictionary<int, object> locks;

    static IntSynchronizer()
    {
        locks = new ConcurrentDictionary<int, object>();
    }

    public static object GetLock(int id)
    {
        return locks.GetOrAdd(id, new object());
    }
}

And then I use it like

lock (IntSynchronizer.GetLock(clientId))
{
    // do stuff (nothing async) that can only be done once per client at any given moment
}

And that works, but apparently you can't use this if you need to make async calls.

So then I found this other way, like this:

private static readonly SemaphoreSlim _someLock = new (1,1);

And then I use that like this

await _someLock.WaitAsync();
try
{
    // do async things that can only be done one at a time
}
finally
{
    _someLock.Release();
}

I need to combine these things. How do I lock on an integer like in the first example, but support async calls, like in the second example?

Christopher
  • 10,409
  • 13
  • 73
  • 97
  • It seems to me it would be enough to make a version of that `IntSynchronizer` class that saves `SemaphoreSlim` instead of `object` in the `ConcurrentDictionary` – Petrusion Apr 30 '22 at 23:11
  • Related: [Asynchronous locking based on a key](https://stackoverflow.com/questions/31138179/asynchronous-locking-based-on-a-key). That question deals with a more specific problem though, with the problem of removing the no longer used locks form the `ConcurrentDictionary`. – Theodor Zoulias Apr 30 '22 at 23:11

1 Answers1

1
public static class AsyncIntSynchronizer
{
    private static readonly ConcurrentDictionary<int, SemaphoreSlim> _semaphores = new();
    public static ValueTask<DisposableRelease> GetAsyncLock(int id)
    {
        return _semaphores
            .GetOrAdd(id, new SemaphoreSlim(1, 1))
            .WaitAsyncScoped();
    }
}

public static class SemaphoreSlimExtensions
{
    public static async ValueTask<DisposableRelease> WaitAsyncScoped(this SemaphoreSlim @this)
    {
        await @this.WaitAsync();
        return new DisposableRelease(@this);
    }
}

public struct DisposableRelease : IDisposable
{
    private readonly SemaphoreSlim _semaphoreSlim;

    public DisposableRelease(SemaphoreSlim semaphoreSlim) => _semaphoreSlim = semaphoreSlim;
    public void Dispose() => _semaphoreSlim.Release();
}

class TestClass
{
    private async Task Main()
    {
        using (await AsyncIntSynchronizer.GetAsyncLock(1))
        {

        }
    }
}

Keep in mind also that consecutives lock from the same thread are accepted (count as 1), but consecutive calls to WaitAsync from the same thread count independently one from the other