在C#2.0中引入了lock语句,以防止多线程同时访问一个对象。
在异步编程模型中,锁的目的是将代码块的并发执行次数限制为定义的次数。
尽管Microsoft引入了许多线程同步机制,但我们仅在本文中讨论SemaphoreSlim。
例
class DataManger
{
private DbSet<string> _users;
public DataManger(DbSet<string> users)
{
_users = users;
}
public async Task AddUser(string username)
{
await _users.AddAsync(username);
}
}
由于某些原因,我们需要将对addUser方法的调用次数限制为一次3次。
static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(3);
public async Task AddUser(string username)
{
await _semaphoreSlim.WaitAsync();
await _users.AddAsync(username);
_semaphoreSlim.Release();
}
static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(3);
semaphoreSlim充当锁,我们通过并发请求的最大数目设置为3个请求初始化它
await _semaphoreSlim.WaitAsync();
如果当前并发请求的数量小于3,则它将减少为1,否则将等待直到其他线程之一释放。
_semaphoreSlim.Release();
只需释放信号量,即可执行任何待处理的请求或即将到来的请求。
使用面向切面的编程
虽然semaphoreSlim看起来易于使用,但要它带来了成本,因为它将更多的样板引入代码中(信号量声明、方法开始处的waitasync语句和结束处的释放),并且更加复杂的是,可以想象_users.AddAsync中的异常,可能一个更好的主意是使用try finally块。
这将对您的代码复杂性产生重大影响,因为您必须为每种方法声明一个信号量以限制对其的访问。
为了使代码更简洁,我更喜欢使用Postsharp切面
[Serializable]
public class MethodLockedAttribute : MethodInterceptionAspect
{
private int maximum_concurrency_number;
private static ConcurrentDictionary<int,SemaphoreSlim> SemaphoreSlimRepo=new ConcurrentDictionary<int, SemaphoreSlim>();
public MethodLockedAttribute(int maximumConcurrencyNumber)
{
maximum_concurrency_number = maximumConcurrencyNumber;
}
public override async Task OnInvokeAsync(MethodInterceptionArgs args)
{
SemaphoreSlim semaphore=new SemaphoreSlim(maximum_concurrency_number);
SemaphoreSlimRepo.GetOrAdd(args.Method.GetMetadataToken(), semaphore);
await semaphore.WaitAsync();
try
{
await args.ProceedAsync();
}
finally
{
semaphore.Release();
}
}
}
并将目标方法装饰为:
[MethodLocked(3)]
public async Task AddUser(string username)
{
await _users.AddAsync(username);
}