I've had to use SemaphoreSlim to ensure single-threaded access to some parts of my code, and would like to make sure that I'm disposing of everything correctly. Suppose I have the following class:
public class Foo
{
private readonly CancellationTokenSource _canceller = new CancellationTokenSource();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
~Foo()
{
Dispose(false);
GC.SuppressFinalize(this);
}
public void Dispose()
{
Dispose(true);
}
protected void Dispose(bool disposing)
{
if (_disposed)
return;
_canceller.Cancel();
if (_disposing)
_semaphore.Dispose();
_disposed = true;
}
public async Task ExecuteAsync()
{
try
{
await _semaphore.WaitAsync(_canceller.Token);
}
catch (OperationCanceledException)
{
throw new ObjectDisposedException(nameof(Foo), "Cannot execute on a Foo after it has been disposed");
}
try
{
// Critical section
}
finally
{
_semaphore.Release();
}
}
}
When I call Dispose(true)
, I'm effectively executing the following lines one after the other:
_canceller.Cancel();
_semaphore.Dispose();
My question is, what happens to any other threads/tasks that are currently waiting for the critical section? Can I guarantee that they will always see the cancellation first, and so will not have a problem with the semaphore being disposed? This hasn't caused any issues so far, but that doesn't mean that it is safe.