0

I'm required to do not allow more than one invocation at a time for an API, based on single query param. Parallel invocation with different values of the query param must be allowed.

I invoke the api through

await client.GetAsync(QueryHelpers.AddQueryString(path, parameters)) 

So, there is an async invocation that do not allows me to use System.Threading.Monitor.TryEnter beacause when I try to release the lock I got the exception:

System.Threading.SynchronizationLockException: Object synchronization method was called from an unsynchronized block of code.

Here's the snippet

 try { Monitor.TryEnter(lockObj, timeout, ref lockTaken); if (lockTaken) { List<PatientData> patients = await RetrievePatientsDataAsync(client, new Dictionary<string, string> { ["customerNo"] = customerNo, ["offset"] = _lindeAPIOptions.Offset, ["pagesize"] = _lindeAPIOptions.Pagesize }); data.Patients = patients; return data; } } finally { // Ensure that the lock is released. if (lockTaken) { Monitor.Exit(lockObj); } } 

The parameter is customerNo. In method RetrievePatientsDataAsync i call the aforementioned

await client.GetAsync(QueryHelpers.AddQueryString(path, parameters)) 
3
  • 1
    For lock part see this duplicate and exception explained why you get exception. Commented Nov 14, 2023 at 13:06
  • 1
    You say parallel invocations are not allowed, but what is the intended behavior for concurrent calls? One fails gracefully? One waits for the other to complete? And you do want to allow concurrent calls with different customerNo? Is the number of different customers limited? Commented Nov 14, 2023 at 13:08
  • @JonasH the call can gracefully fail in case of cuncurrent incotion with the same customerNo. The problem is that the API is very slow and often fails for some reason that I have no visibility into. I guess it can't hold much load. So the requirement is to limit the access by avoiding the parallel access for customerNo Commented Nov 14, 2023 at 13:30

1 Answer 1

1

You could possibly keep a list of all the ids that are "locked", something like this:

private HashSet<long> lockedIds = new(); public async Task ExclusiveAccessById(long id) { bool hasExclusiveAccess = true; lock (lockedIds) { hasExclusiveAccess = lockedIds.Add(id); } if (!hasExclusiveAccess) { // handle failure return; } try { // Handle success await Task.Delay(1); return; } finally { lock (lockedIds) { lockedIds.Remove(id); } } } 

The key part here is to not hold the lock while the actual method is running, or when doing any awaiting etc. You could probably do something similar with a ConcurrentDictionary, but the overhead of a lock should be low if there is low contention.

Sign up to request clarification or add additional context in comments.

2 Comments

@TheodorZoulias Oups, your right, the check is supposed to be before the try/finally.
Than you. I've implemented a thread safe Singleton that contains the HashSet and then it works.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.