我正在尝试了解 c#9 中新的 System.Threading.Lock 对象。
在过去,我曾经使用好的旧锁(对象)来防止多个线程访问代码的同一部分,如下所示:
public class LazyLoadedItem
{
}
private LazyLoadedItem _item = null;
private readonly object MyLock = new object();
public LazyLoadedItem GetItem
{
get
{
{
if (_item != null)
return _item;
lock (MyLock)
{
// Serve the queue of threads that passed the first check when the floodgates opened...
if (_item != null)
return _item;
_item = new LazyLoadedItem(); // get from Database/FileSystem/Web
}
return _item;
}
}
}
public void ClearCache()
{
_item = null;
}
这效果很好,我什至可以使用这个锁在同一个线程中安全地执行递归操作,因为锁是“线程绑定的”。
但是..自
await async
时代以来,以这种方式锁定不再安全,因为一旦需要等待数据库/网络/文件系统或其他任何东西,线程就会被重用。从那时起,我将采用一种使用 SemaphoorSlim
的新模式来确保安全锁定,如下所示:
private readonly SemaphoreSlim MyLock = new SemaphoreSlim(1, 1);
public async Task<LazyLoadedItem> GetItem(CancellationToken requestCancelled)
{
if (_item != null)
return _item;
await MyLock.WaitAsync(requestCancelled); // throws exception when cancelled
try
{
// Serve the queue of threads that passed the first check when the floodgates opened...
if (_item != null)
return _item;
_item = await GetLazyLoadedItem().ConfigureAwait(false); // get from Database/FileSystem/Web
// note that we may be continuing on a different thread here!
}
finally
{
MyLock.Release();
}
return _item;
}
有点偏离主题,但是
await MyLock.WaitAsync(requestCancelled).ConfigureAwait(false);
安全吗?我从来不敢ConfigureAwait(false)
接受这个特别的电话。
无论如何,我关于新
System.Threading.Lock
的问题是它适合哪里?它是“异步安全”吗?
如果不是,我猜它的使用仅限于从不等待任何东西的代码(例如缓存计算,如总数量 * 某些集合的重量),在我看来,这将它的用例减少到如此小的范围,以至于在 c#9 中引入几乎没有道理。我能想到的唯一潜在应用是缓存反射,如下所示:
private static Dictionary<string, PropertyInfo> _props = null;
private static readonly System.Threading.Lock _myLock = new System.Threading.Lock();
public Dictionary<string,PropertyInfo> Props
{
get
{
if (_props != null)
return _props;
lock (_myLock)
{
// Serve the queue of threads that passed the first check when the floodgates opened...
if (_props != null)
return _props;
_props = this.GetType().GetProperties().ToDictionary(p => p.Name);
}
return _props;
}
}
这对于序列化或注入容器可能很有用(只要
GetProperties()
和 ToDictionary()
不是异步的),但由于锁只会在启动期间或首次使用整个进程生命周期的一小部分使用失去了优化点。
那么为什么要推出新的
System.Threading.Lock
呢?我想一般来说绝大多数代码应该坚持SemaphoreSlim
?我错过了什么?
简而言之 性能
当您锁定普通对象时
object
,它会使用监视器对象在外部进行跟踪(否则 C# 中的每个对象都必须包含一个锁)
当您跟踪
System.Threading.Lock
时,所有锁定机制都存储在锁定对象本身内部(通常是原子计数器和要等待的操作系统“锁定”对象),当您在没有共同注意的情况下锁定它时,不应该有间接性。
总之纯粹是为了性能优化。