我正在创建从资源异步读取的内容,但是它应该只读取一次,而其他时候则返回缓存的结果。在WebAPI ASP.NET Core 2.1应用程序中,这种方法每分钟被称为成千上万次。
我想使用某种锁来同步缓存的数据,而没有信号量对象的开销,所以我想知道这是否合适:
private const int LastReadTTL = 5000;
private static int _lastTickCountRead;
private static Models.MyModel _lastRead;
private static readonly object _lock = new object();
private static bool ShouldRead()
{
lock(_lock)
{
var currentTickCount = Environment.TickCount;
if ((_lastRead == null) || (currentTickCount > (_lastTickCountRead + LastReadTTL)) || (currentTickCount < _lastTickCountRead))
{
_lastTickCountRead = currentTickCount;
return true;
}
}
return false;
}
public async Task<Models.MyModel> ReadSomethingAsync()
{
if (ShouldRead())
{
_lastRead = await SomethingToReadAsync.ConfigureAwait(false);
}
return _lastRead;
}
如果这不合适,为什么?在这种情况下使用SemaphoreSlim更合适吗?
谢谢
答案 0 :(得分:2)
此问题属于codereview.stackexchange.com,而不是stackoverflow。如果问题没有解决,我的回答是“否”,通常,锁定异步方法不是一个好的模式。
原因是异步方法被设计为在无法进行处理(通常等待IO完成)时产生控制,因此线程可以切换到其他可以继续执行的任务。通过阻塞线程,它迫使操作系统将上下文切换到另一个线程,稍后当锁已为阻塞的线程准备就绪时,再次执行另一个上下文切换。异步是专门为减少上下文切换的性能而设计的,因此阻止异步方法将失去该功能的优势。
我建议使用任何同步对象来返回可以等待的任务,无论是信号量,AsyncEx nuget包中的内容还是其他内容。希望创建这些脚本的任何人都比您本人对.NET了解更多,因此应该比我们自己实现的任何工具都要好。他们很有可能会使用阻塞的同步对象,但这样做只是为了避免争用情况,当未成功获得锁时,它会迅速退回到异步等待,因此在提供异步保证的同时还提供了异步保证。并发系统。
话虽如此,如果您的用例只是定期更新值,则应查看Interlocked.Exchange,它为您提供了一种无锁的方式来更新值或对象实例。