I am refactoring older synchronous C# code to use an async library. The current synchronous code makes liberal usage of locks. Outer methods often call inner methods, where both lock on the same objects. These are often "protected objects" defined in the base class and locked upon in base virtual methods and the overrides that call the base. For synchronous code, that's ok as the thread entering the outer/override method lock can also enter the inner/base method one. That is not the case for async / SemaphoreSlim(1,1)s.
I'm looking for a robust locking mechanism I can use in the async world that will allow subsequent downstream calls to the same locking object, to enter the lock, as per the behaviour in synchronous "lock {...}" syntax. The closest I have come is semaphore slim, but it is too restrictive for my needs. It restricts access not only to other threads, but to the same thread requesting entrance in the inner call too. Alternatively, is there a way to know that the thread is already "inside" the semaphore before calling the inner SemaphoreSlim.waitasync()?
Answers questioning the design structure of the inner/outer methods both locking on the same object are welcome (I question it myself!), but if so please propose alternative options. I have thought of only using private SemaphoreSlim(1,1)s, and having inheritors of the base class use their own private semaphores. But it gets tricky to manage quite quickly.
Sync Example: Because the same thread is requesting entrance to the lock in both inner and outer, it lets it in and the method can complete.
private object LockObject = new object();
public void Outer()
{
lock (LockObject)
{
foreach (var item in collection)
{
Inner(item);
}
}
}
public void Inner(string item)
{
lock (LockObject)
{
DoWork(item);
}
}
Async Example: The semaphore doesn't work like that, it will get stuck at the first iteration of inner async because it's just a signal, it doesn't let another one pass until it is released, even if the same thread requests it
protected SemaphoreSlim LockObjectAsync = new SemaphoreSlim(1,1);
public async Task OuterAsync()
{
try
{
await LockObjectAsync.WaitAsync();
foreach (var item in collection)
{
await InnerAsync(item);
}
}
finally
{
LockObjectAsync.Release();
}
}
public async Task InnerAsync(string item)
{
try
{
await LockObjectAsync.WaitAsync();
DoWork(item);
}
finally
{
LockObjectAsync.Release();
}
}