I am working on a C# API that is used by a variety of consumers. This API provides access to a shared resource (in my case hardware that does serial communication) , that will often have a few different actors trying to use it concurrently.
The issue I have is that some of my consumers will want to use this in a multi-threaded environment - each actor works independently and try to use the resource. A simple lock works fine here. But some of my consumers would prefer to use async-await and time-slice the resource. (As I understand it) this requires an asynchronous lock to yield the timeslice back to other tasks; blocking at a lock would halt that whole thread.
And I imagine that having serial locks is unperformant at best, and a potential race condition or deadlock at worst.
So how can I protect this shared resource in a common codebase for both potential concurrency usages?
You can use SemaphoreSlim
with 1 as the number of requests. SemaphoreSlim
allows to lock in both an async
fashion using WaitAsync
and the old synchronous way:
await _semphore.WaitAsync()
try
{
... use shared resource.
}
finally
{
_semphore.Release()
}
You can also write your own AsyncLock
based on Stephen Toub's great post Building Async Coordination Primitives, Part 6: AsyncLock. I did it in my application and allowed for both synchronous and asynchronous locks on the same construct.
Usage:
// Async
using (await _asyncLock.LockAsync())
{
... use shared resource.
}
// Synchronous
using (_asyncLock.Lock())
{
... use shared resource.
}
Implementation:
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();
}
}
}
来源:https://stackoverflow.com/questions/21011179/how-to-protect-resources-that-may-be-used-in-a-multi-threaded-or-async-environme