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

时间:2018-12-07 04:46:45

标签: c# async-await thread-synchronization

我正在创建从资源异步读取的内容,但是它应该只读取一次,而其他时候则返回缓存的结果。在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更合适吗?

谢谢

1 个答案:

答案 0 :(得分:2)

此问题属于codereview.stackexchange.com,而不是stackoverflow。如果问题没有解决,我的回答是“否”,通常,锁定异步方法不是一个好的模式。

原因是异步方法被设计为在无法进行处理(通常等待IO完成)时产生控制,因此线程可以切换到其他可以继续执行的任务。通过阻塞线程,它迫使操作系统将上下文切换到另一个线程,稍后当锁已为阻塞的线程准备就绪时,再次执行另一个上下文切换。异步是专门为减少上下文切换的性能而设计的,因此阻止异步方法将失去该功能的优势。

我建议使用任何同步对象来返回可以等待的任务,无论是信号量,AsyncEx nuget包中的内容还是其他内容。希望创建这些脚本的任何人都比您本人对.NET了解更多,因此应该比我们自己实现的任何工具都要好。他们很有可能会使用阻塞的同步对象,但这样做只是为了避免争用情况,当未成功获得锁时,它会迅速退回到异步等待,因此在提供异步保证的同时还提供了异步保证。并发系统。

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