C# 暂停同时进行的REST调用,直到第一个调用完成

C# 暂停同时进行的REST调用,直到第一个调用完成,c#,multithreading,rest,block,pulse,C#,Multithreading,Rest,Block,Pulse,我们有一个REST API方法,类似于: List<item> GetItems(int AccountID) { var x = getFromCache(AccountID); if(x==null) { x = getFromDatabase(AccountID); addToCache(AccountID, x); } return x; } List GetItems(int AccountID) {

我们有一个REST API方法,类似于:

List<item> GetItems(int AccountID)
{
    var x = getFromCache(AccountID);
    if(x==null)
    {
        x = getFromDatabase(AccountID);
        addToCache(AccountID, x);
    }
    return x;
}
List GetItems(int AccountID)
{
var x=getFromCache(AccountID);
如果(x==null)
{
x=getFromDatabase(AccountID);
addToCache(AccountID,x);
}
返回x;
}
对于一些复杂的DB调用,这是一种成本相当高的方法,我们有一种常见的情况,即数百个具有相同AccountId的用户几乎同时进行调用(他们都会收到广播通知)

在该方法中,我们将结果集缓存10秒,因为对于在该窗口内发出请求的每个人来说,一个接近时间的结果是可以接受的。但是,由于它们都在同一时间进行调用(同样,对于特定的AccountID),缓存永远不会提前填充,因此每个人最终都会进行数据库调用

因此,我的问题是,在该方法中,如何暂停特定accountId的所有传入请求,并使它们都等待第一个结果集完成,以便其余调用可以使用缓存的结果集


我已经读了一些关于Monitor.Pulse和Monitor.Lock的内容,但是对于每个accountId锁的实现我有点不知所措。非常感谢您的帮助。

对于具有相同AccountId的请求,您必须锁定相同的对象,但对于每个AccountId,您必须使用不同的对象。下面是如何使用字典跟踪单个AccountID的锁定对象的示例

    Dictionary<int, Object> Locks = new Dictionary<int, object>();

    List<item> GetItems(int AccountID)
    {
        //Get different lock object for each AccountId
        Object LockForAccountId = GetLockObject(AccountID);

        //block subsequent threads until first thread fills the cache
        lock (LockForAccountId)
        {
            var x = getFromCache(AccountID);
            if (x == null)
            {
                x = getFromDatabase(AccountID);
                addToCache(AccountID, x);
            }
            return x;
        }
    }

    private Object GetLockObject(int AccountID)
    {
        Object LockForAccountId;

        //we must use global lock while accessing dictionary with locks to prevent multiple different lock objects to be created for the same AccountId
        lock (Locks)
        {
            if (!Locks.TryGetValue(AccountID, out LockForAccountId))
            {
                LockForAccountId = new Object();
                Locks[AccountID] = LockForAccountId;
            }
        }
        return LockForAccountId;
    }
字典锁=新字典();
列表GetItems(int AccountID)
{
//为每个AccountId获取不同的锁对象
对象LockForAccountId=GetLockObject(AccountID);
//阻止后续线程,直到第一个线程填满缓存
锁(lockforaccounted)
{
var x=getFromCache(AccountID);
如果(x==null)
{
x=getFromDatabase(AccountID);
addToCache(AccountID,x);
}
返回x;
}
}
私有对象GetLockObject(int AccountID)
{
对象锁定帐户;
//在使用锁访问字典时,必须使用全局锁,以防止为同一AccountId创建多个不同的锁对象
锁(锁)
{
如果(!Locks.TryGetValue(AccountID,out LockForAccountId))
{
LockForAccountId=新对象();
Locks[AccountID]=lockforaccounted;
}
}
返回账户锁;
}

您是否考虑过为此使用
Lazy

请尝试以下代码:

private object _gate = new object();
List<item> GetItems(int AccountID)
{
    lock (_gate)
    {
        var x = getFromCache(AccountID);
        if (x == null)
        {
            x = new Lazy<List<item>>(() => getFromDatabase(AccountID));
            addToCache(AccountID, x);
        }
        return x.Value;
    }
}

许多用户使用同一帐户ID的原因是服务帐户ID。。?如果你正在使用SQL Server……,你能改变你的存储过程来使用事务或添加一些<代码> >没有数据库的锁/代码>命令吗?我会考虑一个2级缓存,所以如果你有一个第二个缓存的“未决”最后10-20秒,则当前在挂起缓存中的任何调用都将自阻塞并等待一段时间,然后再尝试真正的db调用。出于类似的原因,您可能同样希望缓存“已证明不存在”。我会避免根据变量的值进行监视和锁定。无论变量的值如何,我都会为代码和内存保留这些锁和同步器。
Lazy<List<item>> getFromCache(int AccountID)
void addToCache(int AccountID, Lazy<List<item>> x)