问题
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