я могу обновить одну сущность в параллельных потоках c# - PullRequest
1 голос
/ 24 апреля 2020

с использованием: Asp. net Core, Entityframework Core, ABP 4.5

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

Моя цель: 1. Конечная точка должна ответить как можно скорее; 2. Длительная инициализация обрабатывается в фоновом режиме;

Код до (второстепенные детали для краткости опущены)

public async Task<ResponceDto> Rgistration(RegModel input)
{       
    var user = await _userRegistrationManager.RegisterAsync(input.EmailAddress, input.Password, false );
    var result = await _userManager.AddToRoleAsync(user, defaultRoleName);
    user.Code = GenerateCode();
    await SendEmail(user.EmailAddress, user.Code);
    await AddSubEntities(user);
    await AddSubCollectionEntities(user);
    await CurrentUnitOfWork.SaveChangesAsync();
    return user.MapTo<ResponceDto>();
}

private async Task AddSubEntities(User user)
{
    var newSubEntity = new newSubEntity { User = user, UserId = user.Id };
    await _subEntityRepo.InsertAsync(newSubEntity); 
    //few another One-to-One entities...
}

private async Task AddSubEntities(User user)
{
    List<AnotherEntity> collection = GetSomeCollection(user.Type);  
    await _anotherEntitieRepo.GetDbContext().AddRangeAsync(collection); 
    //few another One-to-Many collections...
}

Попробуйте изменить:

public async Task<ResponceDto> Rgistration(RegModel input)
{
    var user = await _userRegistrationManager.RegisterAsync(input.EmailAddress, input.Password, false );

    Task.Run(async () => {
        var result = await _userManager.AddToRoleAsync(user, defaultRoleName);          
    });

    Task.Run(async () => {
        user.Code = GenerateCode();
        await SendEmail(user.EmailAddress, user.Code);  
    });

    Task.Run(async () => {
    using (var unitOfWork = UnitOfWorkManager.Begin())
    {//long operation. defalt unitOfWork out of scope
        try
        {
            await AddSubEntities(user);                            
        }            
        finally
        {                
            unitOfWork.Complete();
        }
    }           
});

Task.Run(async () => {
    using (var unitOfWork = UnitOfWorkManager.Begin())
    {
        try
        {
            await AddSubCollectionEntities(user);                            
        }            
        finally
        {                
            unitOfWork.Complete();
        }
    }           
});
    await CurrentUnitOfWork.SaveChangesAsync();
    return user.MapTo<ResponceDto>();
}

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

  1. Вторая операция началась в этом контексте до завершения предыдущей операции. Обычно это вызвано тем, что разные потоки используют один и тот же экземпляр DbContext.

  2. В нескольких вызовах регистрации: Невозможно вставить строку с повторяющимся ключом в объект «XXX» с уникальным индексом «YYY». Значение дубликата ключа (70). Оператор был прерван.

Я думал на сервере каждый запрос в его потоке, но, видимо, нет.

или все пользователи успешно зарегистрированы, но у них нет какой-либо суб-сущности в базе данных. гораздо проще не зарегистрировать пользователя, чем выяснить, где он был инициализирован неправильно = (*

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

Ответы [ 2 ]

4 голосов
/ 24 апреля 2020

Использование Task.Run в ASP. NET редко является хорошей идеей.

Asyn c Методы в любом случае выполняются в пуле потоков, поэтому добавление их в Task.Run просто добавляет накладные расходы без какой-либо выгоды.

Цель использования asyn c в ASP. NET - просто предотвратить блокировку потоков, чтобы они могли обслуживать другие запросы HTTP.

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

Если есть возможность вернуться рано и разрешить некоторым операциям продолжить работу на заднем плане есть подробности здесь , показывающие, как это можно сделать.

3 голосов
/ 24 апреля 2020

Task.Run не совпадает с параллельным. Он берет новый поток из пула и запускает работу над этим потоком, и, поскольку вы этого не ожидаете, остальная часть кода может двигаться дальше. Тем не менее, это потому, что вы, по сути, осиротели в этой теме. Когда действие вернется, все сервисы с определенными областями будут удалены, включая такие вещи, как ваш контекст. Любые потоки, которые еще не завершены, в результате приведут к ошибке.

Пул потоков является ограниченным ресурсом, и в контексте веб-приложения он напрямую равен пропускной способности вашего сервера. Каждый поток, который вы берете, на один запрос меньше, который вы можете обслуживать. В результате вы с большей вероятностью попадете в очередь запросов, что только увеличит время обработки. Практически никогда не целесообразно использовать Task.Run в веб-среде.

Кроме того, EF Core (или старый EF, в этом отношении) не поддерживает распараллеливание. Таким образом, даже без других проблем, описанных выше, это не даст вам холодно делать то, что вы пытаетесь сделать здесь, независимо от того.

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

Скорее всего, замедление происходит из-за отправки электронной почты. Впрочем, это тоже можно оптимизировать. Однажды я находился в ситуации, когда отправка писем занимала 30 секунд, пока я, наконец, не понял, что это проблема с нашим сервером Exchange, когда ИТ-администратор специально ввел 30-секундную задержку. Независимо от этого, как правило, всегда предпочтительнее фоновых вещей, таких как отправка электронных писем, так как они не являются основными для функциональности вашего приложения. Однако это означает, что фактически обрабатывает их в фоновом режиме, то есть ставит их в очередь и обрабатывает их с помощью чего-то вроде размещенной службы или совершенно другого рабочего процесса.

...