C# Lock和await
如果试图在 lock 块中使用 async 关键字时使用 lock 关键字,会得到这个编译错误:cannot await in the body of a lock statement。原因是在async 完成之后,该方法可能会在一个不同的线程中运行,而不是在async 关键字之前。lock 关键字需要同一个线程中获取锁和释放锁。下面的代码块会导致编译错误:static ...
如果试图在 lock 块中使用 async 关键字时使用 lock 关键字,会得到这个编译错误:cannot await in the body of a lock statement。原因是在async 完成之后,该方法可能会在一个不同的线程中运行,而不是在async 关键字之前。lock 关键字需要同一个线程中获取锁和释放锁。
下面的代码块会导致编译错误:
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");
}
}
如何解决这个问题?不能为此使用 Monitor,因为 Monitor 需要从它获取锁的同一线程中释放锁。lock 关键字基于 Monitor。
虽然 Mutex 对象可以用于不同进程之间的同步,但它有相同的问题:它为线程授予了一个锁。从不同的线程中释放锁是不可能的。相反,可以使用 Semaphore 或 SemaphoreSlim 类。Semaphore 可以从不同的线程中释放信号量。
下面的代码片段使用 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();
}
}
下面尝试在多个任务中同时调用此方法。该方法 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();
}
运行该程序,可以看到多个任务同时启动,但是在信号量被锁定后,所有其他任务都需要等待信号量再次释放:
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
为了更容易地使用锁,可以创建一个实现 IDisposable 接口的类来管理资源。对于这个类。可以使用 using 语句,就像使用 lock 状态来锁定和释放信号量一样。
下面的代码片段实现了 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;
}
}
从前面所示的 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");
}
}
使用类似于 LockWithSemaphore 方法的 UseAsyncSemaphore 方法会执行相同的行为。然而,类只编写一次,等待过程中的锁定就变得更简单。
技术群:添加小编微信并备注进群
小编微信:mm1552923
公众号:dotNet编程大全
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)