.Net Core Async critical section if working on same entity

你说的曾经没有我的故事 提交于 2021-01-27 17:51:29

问题


I need to be sure that a method accessed via a web API cannot be accessed by multiple call at the same time if it work on the same object with the same id

I understand the use of SemaphoreSlim but a simple implemetation of that will lock the critical section for all. But I need that section locked only if it works on the same entity and not on 2 different

This is my scenario, an user start to work, the entity is created and is ready to be modified, then one or more user can manipulate this entity, but a part of this manipulation has to be in a critical section or it will lead to inconsistent data, when the work is finished, the entity will be removed from the work status and moved to and archive and can only be accessed readonly

The class which contains that function is injected as transient in the startup of the application

 services.AddTransient<IWorkerService>(f => new WorkerService(connectionString));
public async Task<int> DoStuff(int entityId)
{
  //Not Critical Stuff

  //Critical Stuff

  ReadObjectFromRedis();
  ManipulateObject();
  UpdateSqlDatabase();
  SaveObjectToRedis();

 //Not Critical Stuff
}

How can I achieve that?


回答1:


Try this, I'm not sure if those objects are available in .net-core

class Controller
{
    private static ConcurrentDictionary<int, SemaphoreSlim> semaphores = new ConcurrentDictionary<int, SemaphoreSlim>();

    public async Task<int> DoStuff(int entityId)
    {
        SemaphoreSlim sem = semaphores.GetOrAdd(entityId, ent => new SemaphoreSlim(0, 1));
        await sem.WaitAsync();
        try
        {
            //do real stuff
        }
        finally
        {
            sem.Release();
        }
    }
}



回答2:


This is not an easy problem to solve. I have a similar problem with cache: I want that when cache expires only one call is made to repopulate it. Very common approach for token e.g. that you have to renew every now and then.

A problem with an ordinary use of semaphore is that after you exit, all threads that were waiting will just go in and do the call again, that's why you need double check locking to fix it. If you can have some local state for you case I am not sure (I suppose you do since you have a reason for doing only one call and have state most likely), but here is how I solved it for token cache:

    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);

    public async Task<string> GetOrCreateAsync(Func<Task<TokenResponse>> getToken)
    {
        string token = Get();
        if (token == null)
        {
            await _semaphore.WaitAsync();
            try
            {
                token = Get();
                if (token == null)
                {
                    var data = await getToken();
                    Set(data);
                    token = data.AccessToken;
                }
            }
            finally
            {
                _semaphore.Release();
            }
        }

        return token;
    }

Now I don't really know if it is bullet proof. If it were ordinary double check locking (not async), then it is not, though explanation why is really hard and goes to how processor do multithreading behind the scenes and how they reorder instructions.

But in cache case if there is a double call once in a blue moon is not that big of a problem.

I have not found a better way to do that and this is an example provided by e.g. Scott Hanselman and found few places on Stack Overflow as well.



来源:https://stackoverflow.com/questions/56236196/net-core-async-critical-section-if-working-on-same-entity

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!