Проблемы многопоточности в Entity Framework Core - PullRequest
0 голосов
/ 16 апреля 2019

Я использую Entity Framework как способ связи с базой данных и извлечения / записи информации о ней в приложении ASP.NET CORE. (Используется как очень простой API, выступая в качестве сервера для отдельного приложения.)

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

Я неправильно использую структуру сущностей? Есть ли альтернативный инструмент, который будет использоваться для таких вещей, или я должен просто сделать так, чтобы он использовал один поток (если кто-то может напомнить мне, как), или инкапсулировать все мои действия / конечные точки с помощью оператора блокировки блока?

Независимо от того, как я структурирую свой код, он все время подвержен этим HTTP-запросам, поступающим в одно и то же время, параллельно перемещаясь по моему репозиторию / контексту.

Было бы замечательно, если бы я мог создать какую-то очередь, которая, как я считаю, могла бы сделать инкапсуляция всего в блокировке.

EDIT:

Как ответил vasily.sib , я могу решить эту проблему с помощью токенов параллелизма. Пожалуйста, проверьте его комментарий для некоторой удивительной информации о том, как их использовать!

1 Ответ

0 голосов
/ 16 апреля 2019

Ваша проблема в том, что подобные операции ...

_context.Sessions.FirstOrDefault(s => s.Id == sessionId).PlayerCount += 1;
_context.SaveChanges();

... не атомарны. С FirstOrDefault вы получаете сеанс (который вы разыменовываете без проверки null, так что First будет лучшим вариантом здесь, так как вы получите лучшее сообщение об ошибке). Затем вы сохраняете изменения в следующем шаге. Между этими шагами другой параллельный поток мог измениться и уже сохранить новое значение для PlayerCount.

Существует несколько способов решения этой проблемы, и большинство из них потребуют некоторых изменений на уровне БД.

Один из способов решения этой проблемы - написать хранимую процедуру, которая может выполнять обновление атомарно. Вам понадобится оператор UPDATE, похожий на этот:

UPDATE Sessions
SET PlayerCount = PlayerCount + 1
FROM Sessions
WHERE Id = @SessionId

Если вы не хотите использовать хранимые процедуры, вы можете отправить этот SQL напрямую в БД.

Одна из проблем этого решения заключается в том, что позже вы также выполните этот шаг:

if (thisSession.Size == thisSession.PlayerCount)
{
    thisSession.Status = SessionStatus.Active;
    _context.SaveChanges(); // this can be trimmed and added at the previous scope
}

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

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

Для этого вашему сеансу нужен столбец, который будет содержать счетчик версий. Для SQL Server это будет столбец типа rowversion. Если у вас есть этот столбец, EF может использовать магию оптимистичного параллелизма. EF выбросит DbUpdateConcurrencyException, с которым вам придется справиться. Существуют разные способы устранения ошибки. Один простой способ - повторить всю операцию, пока она не сработает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...