So, a pattern I use very often while working on my UWP app is to use a SemaphoreSlim
instance to avoid race conditions (I prefer not to use lock
as it needs an additional target object, and it doesn't lock asynchronously).
A typical snippet would look like this:
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
public async Task FooAsync()
{
await Semaphore.WaitAsync();
// Do stuff here
Semaphore.Release();
}
With the additional try/finally
block around the whole thing, if the code in between could crash but I want to keep the semaphore working properly.
To reduce the boilerplate, I tried to write a wrapper class that would have the same behavior (including the try/finally
bit) with less code needed. I also didn't want to use a delegate
, as that'd create an object every time, and I just wanted to reduce my code without changing the way it worked.
I came up with this class (comments removed for brevity):
public sealed class AsyncMutex
{
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
public async Task<IDisposable> Lock()
{
await Semaphore.WaitAsync().ConfigureAwait(false);
return new _Lock(Semaphore);
}
private sealed class _Lock : IDisposable
{
private readonly SemaphoreSlim Semaphore;
public _Lock(SemaphoreSlim semaphore) => Semaphore = semaphore;
void IDisposable.Dispose() => Semaphore.Release();
}
}
And the way it works is that by using it you only need the following:
private readonly AsyncMutex Mutex = new AsyncMutex();
public async Task FooAsync()
{
using (_ = await Mutex.Lock())
{
// Do stuff here
}
}
One line shorter, and with try/finally
built in (using
block), awesome.
Now, I have no idea why this works, despite the discard operator being used.
That discard _ was actually just out of curiosity, as I knew I should have just written var _
, since I needed that IDisposable
object to be used at the end of the using
block, and not discarder.
But, to my surprise, the same IL is generated for both methods:
.method public hidebysig instance void T1() cil managed
{
.maxstack 1
.locals init (
[0] class System.Threading.Tasks.AsyncMutex mutex,
[1] class System.IDisposable V_1
)
IL_0001: newobj instance void System.Threading.Tasks.AsyncMutex::.ctor()
IL_0006: stloc.0 // mutex
IL_0007: ldloc.0 // mutex
IL_0008: callvirt instance class System.Threading.Tasks.Task`1<class System.IDisposable> System.Threading.Tasks.AsyncMutex::Lock()
IL_000d: callvirt instance !0/*class System.IDisposable*/ class System.Threading.Tasks.Task`1<class System.IDisposable>::get_Result()
IL_0012: stloc.1 // V_1
.try
{
// Do stuff here..
IL_0025: leave.s IL_0032
}
finally
{
IL_0027: ldloc.1 // V_1
IL_0028: brfalse.s IL_0031
IL_002a: ldloc.1 // V_1
IL_002b: callvirt instance void System.IDisposable::Dispose()
IL_0031: endfinally
}
IL_0032: ret
}
The "discarder" IDisposable
is stored in the field V_1
and correctly disposed.
So, why does this happen? The docs don't say anything about the discard operator being used with the using
block, and they just say the discard assignment is ignored completely.
Thanks!
using(await Mutex.Lock())
. β Torinousing
block docs page doesn't say anything about it not needing an explicit variable, and I've never seen it used that was, so I had no clue. If you post this as answer I'll be happy to mark it as valid! β Bland_
is not an operator, it's an identifier. β Catachresis.ConfigureAwait(false)
after theawait
, and remove the finalizer inside theLock
class. β BurchellConfigureAway(false)
cause crashes there, as it'd throw away the original context? Also, what's wrong with the finalizer, isn't that part of the suggestedIDisposable
implementation? Thanks! β BlandLock()
method (if needed), and will return to it whenLock()
completes. There is no need to capture the context insideLock()
, since it doesn't use any UI objects. Therefore, your current implementation would cause a deadlock as soon as someone callsLock().Result
on the UI thread. Also, you should never try to clean up any managed objects inside a finalizer, because the referred object might have already been finalized. I suggest to read about the recommendedIDisposable
implementation more carefully. Good luck! β Burchell