暂停同步 REST 调用,直到第一个完成
Pause simultaneous REST calls until first one completes
我们有一个 REST API 方法类似于:
List<item> GetItems(int AccountID)
{
var x = getFromCache(AccountID);
if(x==null)
{
x = getFromDatabase(AccountID);
addToCache(AccountID, x);
}
return x;
}
这是一个相当昂贵的方法,有一些复杂的数据库调用,我们有一个常见的情况,数百个具有相同 AccountId 的用户几乎同时进行调用(他们都被广播通知)。
在该方法中,我们将结果集缓存 10 秒,因为近期结果对于在 window 内发出请求的每个人来说都很好。但是,由于他们都同时调用(同样,对于特定的 AccountID)缓存不会预先填充,因此每个人最终都会调用数据库。
所以我的问题是,在该方法中,如何暂停所有传入请求 特定 accountId 并让它们都等待第一个结果集完成,以便其余的调用可以使用缓存的结果集吗?
我已经阅读了一些关于 Monitor.Pulse 和 Monitor.Lock 的内容,但是我对 per-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;
}
您是否考虑过为此使用 Lazy<T>
?
试试这个代码:
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;
}
}
您需要更改 getFromCache
& addToCache
以具有以下签名:
Lazy<List<item>> getFromCache(int AccountID)
void addToCache(int AccountID, Lazy<List<item>> x)
我们有一个 REST API 方法类似于:
List<item> GetItems(int AccountID)
{
var x = getFromCache(AccountID);
if(x==null)
{
x = getFromDatabase(AccountID);
addToCache(AccountID, x);
}
return x;
}
这是一个相当昂贵的方法,有一些复杂的数据库调用,我们有一个常见的情况,数百个具有相同 AccountId 的用户几乎同时进行调用(他们都被广播通知)。
在该方法中,我们将结果集缓存 10 秒,因为近期结果对于在 window 内发出请求的每个人来说都很好。但是,由于他们都同时调用(同样,对于特定的 AccountID)缓存不会预先填充,因此每个人最终都会调用数据库。
所以我的问题是,在该方法中,如何暂停所有传入请求 特定 accountId 并让它们都等待第一个结果集完成,以便其余的调用可以使用缓存的结果集吗?
我已经阅读了一些关于 Monitor.Pulse 和 Monitor.Lock 的内容,但是我对 per-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;
}
您是否考虑过为此使用 Lazy<T>
?
试试这个代码:
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;
}
}
您需要更改 getFromCache
& addToCache
以具有以下签名:
Lazy<List<item>> getFromCache(int AccountID)
void addToCache(int AccountID, Lazy<List<item>> x)