替代lock语句块实现await独占访问

C#中有lock语句块和Monitor类可以实现线程锁,实现对资源的独占访问。但是,当像下面这样试图在lock语句块中使用await时会出现错误,无法生成:

lock (streamReader)
{
    await streamReader.ReadLineAsync();
} //编译错误

此时,也不能自作聪明地使用Monitor类实现线程锁,那样虽然能够生成,但是执行结果与你设想的是不同的。像下面这样的代码并不能实现独占访问:

Monitor.Enter(streamReader);
await streamReader.ReadLineAsync();
Monitor.Exit(streamReader); //不能实现功能

为什么这样的异步调用并不能用lock语句块实现独占访问呢?这是因为lock语句块和Monitor类的锁是与线程相关的,加锁之后相同线程依然能够访问,但必须由同一个线程进行解锁后其他线程才能访问。如果有异步调用,加锁、解锁通常都是在UI线程进行,但是对资源的访问通常是在另外一个线程进行的,这样锁并不能起到只允许独占访问的作用。

解决方案是:使用SemaphoreSlim类作为替代方案。Semaphore是旗语的意思,SemaphoreSlim类可以通过一个信号量限制同时访问一个资源的数量。SemaphoreSlim有一个计数器,当调用Release()方法时计数器加一,调用WaitAsync()是会等待到计数器至少为1时对其减一。

SemaphoreSlim semaphore = new Semaphore(1, 1); //初始值为1
await semaphore.WaitAsync(); //将计数器减1
await streamReader.ReadLineAsync();
semaphore.Release(); //将计数器加1

以上代码能够实现同一时刻只能进行一个ReadLineAsync操作。但是,如果ReadLineAsync时发生崩溃的话,计数器将一直停留在0,会造成死锁。所以可以用try语句块包装,并在finally语句块中Release:

await semaphore.WaitAsync(); //将计数器减1
try
{
    await streamReader.ReadLineAsync();
}
finally
{
    semaphore.Release(); //将计数器加1
}

留言

有想法?请给我们留言!您的留言不会直接显示在网站内。