Как защитить ресурсы, которые могут использоваться в многопоточной или асинхронной среде?

Я работаю над С# API, который используется различными потребителями. Этот API обеспечивает доступ к совместно используемому ресурсу (в моем случае аппаратное обеспечение, которое выполняет последовательную связь), в котором часто бывают несколько разных участников, пытающихся использовать его одновременно.

Проблема заключается в том, что некоторые из моих потребителей захотят использовать это в многопоточной среде - каждый актер работает независимо и пытается использовать этот ресурс. Здесь легко работает простой замок. Но некоторые из моих потребителей предпочли бы использовать async-wait и time-slice ресурс. (Как я понимаю) для этого требуется асинхронная блокировка для возврата времени к другим задачам; блокировка блокировки остановит весь поток.

И я полагаю, что в лучшем случае наличие несовместимых серийных замков и потенциальное состояние гонки или тупик в худшем случае.

Итак, как я могу защитить этот общий ресурс в общей кодовой базе для как потенциальных concurrency обычаев?

Ответ 1

Вы можете использовать SemaphoreSlim с 1 числом запросов. SemaphoreSlim позволяет блокировать как стиль async, используя WaitAsync, так и старый синхронный способ:

await _semphore.WaitAsync()
try
{
    ... use shared resource.
}
finally
{
    _semphore.Release()
}

Вы также можете написать свой собственный AsyncLock на основе Stephen Toub great post Построение примитивов для координации Async, часть 6: AsyncLock. Я сделал это в своем приложении и допустил как синхронные, так и асинхронные блокировки в той же конструкции.

Использование:

// Async
using (await _asyncLock.LockAsync())
{
    ... use shared resource.
}

// Synchronous
using (_asyncLock.Lock())
{
    ... use shared resource.
}

Реализация:

class AsyncLock
{
    private readonly Task<IDisposable> _releaserTask;
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
    private readonly IDisposable _releaser;

    public AsyncLock()
    {
        _releaser = new Releaser(_semaphore);
        _releaserTask = Task.FromResult(_releaser);
    }
    public IDisposable Lock()
    {
        _semaphore.Wait();
        return _releaser;
    }
    public Task<IDisposable> LockAsync()
    {
        var waitTask = _semaphore.WaitAsync();
        return waitTask.IsCompleted
            ? _releaserTask
            : waitTask.ContinueWith(
                (_, releaser) => (IDisposable) releaser,
                _releaser,
                CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously,
                TaskScheduler.Default);
    }
    private class Releaser : IDisposable
    {
        private readonly SemaphoreSlim _semaphore;
        public Releaser(SemaphoreSlim semaphore)
        {
            _semaphore = semaphore;
        }
        public void Dispose()
        {
            _semaphore.Release();
        }
    }
}