1

Came to ask the community for help. I have an API endpoint that will handle saving user favourite items. Due to end system limitation, user data needs to be saved(fav item added/removed), before next favourite item is processed. But on the front end obviously can trigger multiple request if user quickly clicks on different items. And this is where it fails as backend cannot have one user data opened by multiple threads. Been looking into solution with Lock, but no luck. to confirm this was the issue I've added Thread.Sleep() to first method and with that it is working. (well, unless user clicks 3rd time). This is obviously a wrong solution.

Q: How can I pause a method (that handles user saving) until the previous one for the user is completed?

Just to clarify I can have user1,user2,user3 making request at the same time. that is not a problem. Issue is if I have user1 making 2-3 request (e.g. to remove ItemA, add ItemB, remove ItemC) as front end would enable to do so - and that is not changing.

public class ApiController{ 

//called from frontend
        [HttpPost]
        [Route("toggleUserFavourites/")]
        public async Task<HttpResponseMessage> toggleUserFavourites(FavouriteData data)
        {
            //here is just testing theory that this is actually wrong. If I wait all works fine. 
            if(currentRequest.Contains(data.userId)){
                Thread.Sleep(6000);
            }
           currentRequest.Add(data.userId);
           return await toggleUserFavouritesHandleEntry(data);
        
        }

//Handle user saving data
public async Task<HttpResponseMessage> toggleUserFavouritesHandleEntry(FavouriteData data){

            //CODE THAT TALKS TO BACKEND TO GET EXISTING USER DATA AND awaits response -  2-4 secs
          
            try {
                user.Save();
                var response = Request.CreateResponse(HttpStatusCode.OK);
                return Request.CreateResponse(HttpStatusCode.OK,  "User updated ");                    
            }
            catch (Exception ex){
                return Request.CreateResponse(HttpStatusCode.Forbidden,  "Error saving");                 
            }
        }

for reference

public class FavouriteData{
        public string userId {get; set;}
        public string itemId {get; set;}
    }
ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
JaroF
  • 68
  • 7
  • 1
    I think, in the api end you should try to implement message queue system. So there is no chance any request to be missed and it should be maintained in the queue one by one for any number of requests by user. – AT-2017 Jun 26 '21 at 16:20
  • Two things :- 1 - Until your front end request gets completed you can show a loader and disable the button. So the user can't click multiple times and also the user know that something going on. 2- On the backend/db you can check if the item already deleted or added then if yes then you can respond to ui with other status/message. – Saurabh bhatia Jun 26 '21 at 16:33
  • 1
    Adding to @AT-2017 answer, implement producer-consumer pattern, where api calls are producers and worker thread is consumer. As a starting point go with single worker thread that will process requests from all users. Then you can improve the flow by creating a pool of consumers that will guarantee that items of same user always processed by same consumer. – Artur Jun 26 '21 at 16:38
  • Saurabh bhatia - yes that is the case for the item clicked, but not for the rest of items as it wouldn't be good UI. So user click item1 that cannot be clicked until processed but they can still click on item1,3... – JaroF Jun 26 '21 at 17:55
  • 1
    It seems that you need an asynchronous keyed locker. Which is a dictionary of `SemaphoreSlim`s, one per user, that can be awaited asynchronously. For implementations check out this question: [Asynchronous locking based on a key](https://stackoverflow.com/questions/31138179/asynchronous-locking-based-on-a-key) – Theodor Zoulias Jun 27 '21 at 05:23

0 Answers0