Best practice for handling async dispose using lock/semaphore

Chris Stopher 6 Reputation points
2020-12-04T20:19:51.727+00:00

I have run into an issue where I have a async method that disposes of resources asynchronously. This method needs to be multithreaded as potentially it can be called at the same time depending on circumstances. Using a plain lock won't work because you can't await inside of a lock. I decided to try a SemaphoreSlim as that has a async method. One thing I'm not sure about is disposing of the Semaphore at the same time that something could be waiting to enter. Is it sufficient to catch a diposed exception and ignore? Whats the best practice to handle this kind of scenario? I will include a brief code example of the situation.

public async Task StopProcess()
        {
            try
            {
                // TODO: not sure what happens if I dispose of this while another thread is waiting to execute. Does the waiting thread throw a obj disposed exception?
                await _stopSemaphore.WaitAsync();

                if (Disposed)
                {
                    return;
                }


                await this.DisposeAsync();

            }
            catch (ObjectDisposedException)
            {
                // do nothing
            }
            finally
            {
                try
                {
                    _stopSemaphore.Release();
                    _stopSemaphore.Dispose();
                }
                catch (ObjectDisposedException)
                {
                    // do nothing
                }
            }
        }

    public virtual async ValueTask DisposeAsync(bool disposing)
    {
        if (Disposed)
        {
            return;
        }

        if (disposing)
        {

            try
            {
                _tokenSource.Cancel();

                // wait till task finishes after canceling token
                await _task;
            }
            finally
            {
                _tokenSource.Dispose();
                await ManagedResource.DisposeAsync();
            }
        }

        Disposed = true;
    }
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,280 questions
0 comments No comments
{count} vote

1 answer

Sort by: Most helpful
  1. Daniel Zhang-MSFT 9,616 Reputation points
    2020-12-07T06:23:07.747+00:00

    Hi ChrisStopher-0454,
    The System.Threading.SemaphoreSlim is a lightweight, fast semaphore that is provided by the CLR and used for waiting within a single process when wait times are expected to be very short.
    And calling WaitAsync on the semaphore produces a task that will be completed when that thread has been granted access to the Semaphore.
    When the task is ready, release the semaphore. It is vital to always release the semaphore when we are ready, or else we will end up with a Semaphore that is forever locked.
    This is why it is important to do the Release within a try...finally clause; program execution may crash or take a different path, this way you are guaranteed execution.
    You should note that you can use Dispose() only when all other operations on SemaphoreSlim have completed.
    More discusses in these threads you can refer to.
    Need to understand the usage of SemaphoreSlim
    SemaphoreSlim.WaitAsync before/after try block
    Is it necessary to call Dispose on a Semaphore?
    Best Regards,
    Daniel Zhang


    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.