Come proteggere le risorse che possono essere utilizzate in un ambiente multi-thread o asincrono?

Sto lavorando su un’API C # utilizzata da una varietà di consumatori. Questa API fornisce l’accesso a una risorsa condivisa (nel mio caso l’hardware che fa comunicazione seriale), che spesso ha alcuni attori diversi che cercano di usarlo contemporaneamente.

Il problema che ho è che alcuni dei miei consumatori vorranno usarlo in un ambiente multi-thread: ogni attore lavora in modo indipendente e tenta di utilizzare la risorsa. Un semplice blocco funziona bene qui. Ma alcuni dei miei consumatori preferirebbero usare async-await e time-slice della risorsa. (A quanto ho capito) questo richiede un blocco asincrono per restituire il timeslice ad altre attività; bloccare a una serratura fermerebbe quell’intero thread.

E immagino che avere serrature seriali sia al meglio non performante, e una potenziale condizione di competizione o stallo nel peggiore dei casi.

Quindi, come posso proteggere questa risorsa condivisa in una base di codice comune per entrambi i potenziali usi della concorrenza?

Puoi utilizzare SemaphoreSlim con 1 come numero di richieste. SemaphoreSlim consente di bloccare sia in modo async utilizzando WaitAsync e il vecchio modo sincrono:

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

Puoi anche scrivere il tuo AsyncLock basato sul grande post Building Beginning Primitives di Stephen Toub , Parte 6: AsyncLock . L’ho fatto nella mia applicazione e ho permesso sia i blocchi sincroni che asincroni sullo stesso costrutto.

Uso:

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

Implementazione:

 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(); } } }