.Net Core Асинхронный критический раздел, если работает над тем - PullRequest
2 голосов
/ 21 мая 2019

Мне нужно быть уверенным, что метод, доступ к которому осуществляется через веб-API, не может быть доступен для нескольких вызовов одновременно, если он работает с одним и тем же объектом с тем же идентификатором

Я понимаю использование SemaphoreSlim но простая реализация этого заблокирует критическую секцию для всех.Но мне нужно заблокировать этот раздел, только если он работает на одной и той же сущности, а не на двух разных

Это мой сценарий, пользователь начинает работать, сущность создается и готова к изменению, затем один илибольше пользователей могут манипулировать этой сущностью, но часть этой манипуляции должна находиться в критической секции, или это приведет к противоречивым данным, когда работа будет завершена, сущность будет удалена из рабочего статуса и перемещена в и заархивирована и можетдоступ только для чтения

Класс, содержащий эту функцию, внедряется как переходный процесс при запуске приложения

 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
}

Как этого добиться?

Ответы [ 2 ]

1 голос
/ 21 мая 2019

Попробуйте, я не уверен, что эти объекты доступны в .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();
        }
    }
}
0 голосов
/ 21 мая 2019

Это не простая проблема, чтобы решить. У меня похожая проблема с кешем: я хочу, чтобы по истечении срока действия кеша был сделан только один вызов для его повторного заполнения. Очень распространенный подход к токену, например что вы должны обновлять время от времени.

Проблема с обычным использованием семафора заключается в том, что после выхода все ожидающие потоки просто входят и снова выполняют вызов, поэтому для его исправления требуется двойная проверка блокировки. Если у вас может быть какое-то локальное состояние для вашего случая, я не уверен (я полагаю, что у вас есть, поскольку у вас есть причина сделать только один вызов и, скорее всего, есть состояние), но вот как я решил это для кэша токена:

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

Теперь я не знаю, пуленепробиваемые. Если бы это была обычная блокировка с двойной проверкой (не асинхронная), то это не так, хотя объяснение, почему это действительно сложно, объясняет, как процессор выполняет многопоточность за кулисами и как они переупорядочивают инструкции.

Но в случае с кешем, если в голубой луне происходит двойной вызов один раз, это не такая большая проблема.

Я не нашел лучшего способа сделать это, и это пример, предоставленный, например, Скотт Хансельман также нашел несколько мест на переполнении стека.

...