Pause concurrent REST calls until first terminated

We have a REST API method similar to:

List<item> GetItems(int AccountID) { var x = getFromCache(AccountID); if(x==null) { x = getFromDatabase(AccountID); addToCache(AccountID, x); } return x; } 

This is a rather expensive method with some complex database calls, and we have a general situation where hundreds of users with the same AccountId will call almost simultaneously (they are all notified of broadcast transmission).

In the method, we cache the result set for 10 seconds, since a result close to time is suitable for everyone who makes a request in this window. However, since they all make a call at the same time (again, for a specific account identifier), the cache is never populated in front, so everyone ends up calling the database.

So my question is, how can I suspend all incoming requests for a specific account within the method and make them all wait for the first result set to complete so that the rest of the calls can use the cached result set?

I got a little familiar with Monitor.Pulse and Monitor.Lock, but the implementation for locking for each account eludes me. Any help would be greatly appreciated.

+5
source share
2 answers

You must block the same object for requests with the same AccountId, but use a different object for each individual account. Here is an example of using a dictionary to track a lock object for individual accounts.

  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; } 
+2
source

Have you ever thought about using Lazy<T> for this?

Try this code:

 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; } } 

You will need to change getFromCache and addToCache to the following signatures:

 Lazy<List<item>> getFromCache(int AccountID) void addToCache(int AccountID, Lazy<List<item>> x) 
0
source

Source: https://habr.com/ru/post/1233295/


All Articles