¿Cómo proteger los recursos que se pueden usar en un entorno multiproceso o asíncrono?

Estoy trabajando en una API de C # que es utilizada por una variedad de consumidores. Esta API proporciona acceso a un recurso compartido (en mi caso, hardware que hace comunicación en serie), que a menudo tendrá algunos actores diferentes que intenten usarlo al mismo tiempo.

El problema que tengo es que algunos de mis consumidores querrán usar esto en un entorno de subprocesos múltiples: cada actor trabaja independientemente y trata de usar el recurso. Un simple candado funciona bien aquí. Pero algunos de mis consumidores preferirían usar async-await y time-slice del recurso. (Según tengo entendido), esto requiere un locking asincrónico para devolver el ciclo de tiempo a otras tareas; el locking en una cerradura detendría todo el hilo.

Y me imagino que tener cerraduras en serie es, en el mejor de los casos, poco eficiente y, en el peor, una posible condición de carrera o punto muerto.

Entonces, ¿cómo puedo proteger este recurso compartido en una base de código común para ambos posibles usos de concurrencia?

Puede usar SemaphoreSlim con 1 como número de solicitudes. SemaphoreSlim permite bloquear tanto la moda async usando WaitAsync como la antigua forma síncrona:

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

También puedes escribir tu propio AsyncLock basado en la gran publicación de Stephen Toub Construyendo primitivas de coordinación Async, Parte 6: AsyncLock . Lo hice en mi aplicación y permití tanto lockings síncronos como asíncronos en la misma construcción.

Uso:

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

Implementación:

 class AsyncLock { private readonly Task _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 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(); } } }