C# Lock和await

5da584f65f70c54e44c764ffe1b5362a.png

如果试图在 lock 块中使用 async 关键字时使用 lock 关键字,会得到这个编译错误:cannot await in the body of a lock statement。原因是在async 完成之后,该方法可能会在一个不同的线程中运行,而不是在async 关键字之前。lock 关键字需要同一个线程中获取锁和释放锁。

047467791deeb9778d3ef57f0cfee3db.png

下面的代码块会导致编译错误:

static async Task IncorrectLockAsync()
{
  lock (s_syncLock)
  {
    Console.WriteLine($"{nameof(IncorrectLockAsync)} started");
    await Task.Delay(500);  // compiler error: cannot await in the body
      // of a lock statement
    Console.WriteLine($"{nameof(IncorrectLockAsync)) ending");
  }
}

d02b005307f21a4732a214ec6697c9df.png

如何解决这个问题?不能为此使用 Monitor,因为 Monitor 需要从它获取锁的同一线程中释放锁。lock 关键字基于 Monitor。

4acd47f70dc34cfc7fcef114c7ea8bb7.png

虽然 Mutex 对象可以用于不同进程之间的同步,但它有相同的问题:它为线程授予了一个锁。从不同的线程中释放锁是不可能的。相反,可以使用 Semaphore 或 SemaphoreSlim 类。Semaphore 可以从不同的线程中释放信号量。

d76bf771c5465c32cd097ab9bbd7f3cf.png

下面的代码片段使用 SemaphoreSlim 对象上的 WaitAsync 等待获得一个信号量。SemaphoreSlim 对象初始化为计数 1,因此对信号量的等待只授予一次。在finally代码块中,通过调用Release方法释放信号量:

private static SemaphoreSlim s_asyncLock = new SemaphoreSlim(1); 
static async Task LockWithSemaphore(string title)
{
  Console.WriteLine($"{title} waiting for lock"); 
  await s_asyncLock.WaitAsync(); 
  try
  {
    Console.WriteLine($"{title} {nameof(LockWithSemaphore)} started"); 
    await Task.Delay(500);
    Console.WriteLine($"{title} {nameof(LockWithSemaphore)} ending");
  }
  finally
  {
    s_asyncLock.Release();
  }
}

a569973388ffb55b857a0129ae7d2fe7.png

下面尝试在多个任务中同时调用此方法。该方法 RunUseSemaphoreAsync 启动6 个任务,并发地调用 LockWithSemaphore 方法:

static async Task RunUseSemaphoreAsync()
{
  Console.WriteLine(nameof(RunUseSemaphoreAsync));
  string[] messages = ( "one", "two", "three", "four", "five", "six" }; 
  Task[] tasks = new Task[messages.Length];
  for (int i = 0; i < messages.Length; i++)
  {
    string message = messages[i];
      
    tasks[i] = Task.Run(async() =>
    {
      await LockWithSemaphore(message);
    }) ;
  }
    
  await Task.WhenAll(tasks); 
  Console.WriteLine();
}

fa37e7ad59ddba41a2d4fe57163884a0.png

运行该程序,可以看到多个任务同时启动,但是在信号量被锁定后,所有其他任务都需要等待信号量再次释放:

RunLockWithAwaitAsync 
two waiting for lock
two LockWithSemaphore started 
three waiting for lock 
five waiting for lock 
four waiting for lock 
six waiting for lock 
one waiting for lock
two LockWithSemaphore ending
three LockWithSemaphore started 
three LockWithSemaphore ending 
five LockWithSemaphore started 
five LockWithSemaphore ending 
four LockWithSemaphore started 
four LockWithSemaphore ending 
six LockWithSemaphore started 
six LockWithSemaphore ending
one LockWithSemaphore started 
one LockWithSemaphore ending

db3dd99ac88592ea1177449c2a0cc0c5.png

为了更容易地使用锁,可以创建一个实现 IDisposable 接口的类来管理资源。对于这个类。可以使用 using 语句,就像使用 lock 状态来锁定和释放信号量一样。

aba263507d15361c8bb9711e47ae8c82.png

下面的代码片段实现了 AsyncSemaphore 类,该类在构造函数中分配一个SemaphoreSlim,在 AsyncSemaphore 上调用 WaitAsync 方法时,返回实现接口 IDisposable 的内部类 SemaphoreReleaser。调用 Dispose 方法时,释放信号量:

public sealed class AsyncSemaphore
{
  private class SemaphoreReleaser : IDisposable
  {
    private SemaphoreSlim _semaphore;
    
    public SemaphoreReleaser(SemaphoreSlim semaphore) => 
      _semaphore = semaphore;
      
    public void Dispose() => _semaphore.Release();
  }


  private SemaphoreSlim _semaphore; 
  public AsyncSemaphore() =>
    _semaphore = new SemaphoreSlim(1);
    
  public async Task<IDisposable> WaitAsync()
  {
    await _semaphore.WaitAsync();
    return new SemaphoreReleaser(_semaphore) as IDisposable;
  }
}

4683e52dfc1f0868badb93281423ff61.png

从前面所示的 LockWithSemaphore 方法中更改实现,现在可以使用 using 语句锁定信号量。记住,using 语句创建一个 catch/finally 块,在 finally 块中调用 Dispose 方法:

private static AsyncSemaphore s_asyncSemaphore = new AsyncSemaphore(); 
static async Task UseAsyncSemaphore(string title)
{
  using (await s_asyncSemaphore.WaitAsync())
  {
    Console.WriteLine($"{title}  {nameof(LockWithSemaphore)} started"); 
    await Task.Delay(500);
    Console.WriteLine($"{title} {nameof(LockWithSemaphore)} ending");
  }
}

c23fbf07ef8ca82cca6538fdf5ec67f3.png

使用类似于 LockWithSemaphore 方法的 UseAsyncSemaphore 方法会执行相同的行为。然而,类只编写一次,等待过程中的锁定就变得更简单。

技术群:添加小编微信并备注进群

小编微信:mm1552923   

公众号:dotNet编程大全    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值