从异步方法锁定的调用方法。这样合适吗?

Calling method that locks from async method. Is this proper?

我正在创建一些从资源中异步读取的东西,但它应该只每隔一段时间读取一次,其他时候 return 缓存的结果。在 WebAPI ASP.NET Core 2.1 应用程序中,此方法每分钟调用 10 次数千次。

我想在没有信号量对象开销的情况下使用某种锁来同步缓存数据,所以我想知道这是否合适:

    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 更合适吗?

谢谢

这个问题属于 codereview.stackexchange.com 而不是 Whosebug。万一问题没有得到解决,我的回答是否定的,通常锁定异步方法不是一个好的模式。

原因是异步方法被设计为在无法进行时(通常是等待 IO 完成)让出控制权,因此线程可以切换到其他准备继续的任务。通过阻塞线程,它会强制操作系统将上下文切换到另一个线程,稍后当锁为被阻塞的线程准备好时,再次进行另一个上下文切换。异步是专门为减少上下文切换的性能损失而设计的,因此阻止异步方法会消除该功能的好处。

我建议使用任何 returns 您可以等待任务的同步对象,无论是 semaphoreslim、AsyncEx nuget 包中的东西,还是其他任何对象。希望创建这些的任何人都比你或我更了解 .NET 中的异步,因此应该比我们自己实现的任何东西都更好。他们很有可能会使用阻塞同步对象,但这样做只是为了避免竞争条件,并在未成功获取锁时快速回退到异步等待,因此在提供异步所需的同步保证的同时提供异步的优势并发系统。

话虽如此,如果您的用例只是定期更新一个值,您应该查看 Interlocked.Exchange,它为您提供了一种更新值或对象实例的无锁方式。