Я использую ASP.NET Core Identity, с помощью приложения Jmeter я отправляю несколько запросов в свое веб-приложение для создания пользователя, и оно создает несколько пользователей с одним и тем же адресом электронной почты, хотя мой RequireUniqueEmail равен true
services.AddIdentity<User, Role>(identityOptions =>
{
identityOptions.User.RequireUniqueEmail = true;
});
Когда я отправляю запросы один за другим, все в порядке, и в этом коде мне выдается повторяющаяся ошибка электронной почты.
var result = await _userManager.CreateAsync(userToCreate, userPassword);
Но когда я отправляю несколько запросов с помощью Jmeter, приведенный выше код не может обрабатывать повторяющиеся электронные письма, и после завершения запросов Jmeter я открываю SQL-сервер и вижу, что создано как минимум 10 записей с одним и тем же адресом электронной почты.
Я искал эту проблему и нашел это решение Using lock
private static readonly object lockObj = new object();
lock(lockObj)
{
var result = await _userManager.CreateAsync(userToCreate, userPassword);
}
Но он выдает мне ошибку и говорит: «Вы не можете использовать await в операторе блокировки», а вторая проблема заключается в том, что в ASP.NET Core Identity нет метода синхронизации для создания. Как я могу создать метод синхронизации создания в ASP.NET Core Identity?





Блокировки привязаны к потоку, а продолжение await может выполняться в любом потоке, что нарушает механизм блокировки .NET.
Вам также следует использовать асинхронные методы в ядре asp.net, поэтому вам нужно заменить оператор lock, а не метод.
Есть хорошая библиотека Nito.AsyncEx, которая предоставляет асинхронный мьютекс:
private static readonly AsyncLock _mutex = new AsyncLock();
public async Task<SomeType> Something(Type1 userToCreate,Type2 userPassword)
{
// AsyncLock can be locked asynchronously
using (await _mutex.LockAsync())
{
return await _userManager.CreateAsync(userToCreate, userPassword);
}
}
прежде всего, вы не можете использовать оператор lock с ключевым словом await!
и в вашем случае, возможно, лучше использовать объект SemaphoreSlim, потому что в критическую секцию (между WaitAsync и Release) может войти только один запрос одновременно!
private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(1);
// rest of your code
async Task CreateUserAsync(User userToCreate, string userPassword)
{
await semaphore.WaitAsync();
try
{
var result = await _userManager.CreateAsync(userToCreate, userPassword);
}
finally
{
semaphore.Release();
}
}
а если вам нужна синхронная версия, вы также можете создать метод-оболочку, которая инкапсулирует асинхронный вызов и блокирует выполнение до завершения операции.
Вы должны обеспечить это в базе данных, а не с помощью блокировок или семафоров в коде. Представьте себе, что вашему приложению необходимо масштабироваться, например, на несколько серверов.
Добавьте UNIQUE constraint в столбец электронной почты в базе данных. При добавлении нового пользователя вы получите сообщение об ошибке, если адрес электронной почты уже существует, и вы можете обработать его в своем приложении.
Вы можете использовать асинхронный метод внутри оператора блокировки в Task или в анонимном лямбда-методе (()=>{});. Объект Task можно запустить и перевести в состояние ожидания ответа с помощью метода Task.Wait(). Эта процедура гарантирует, что все, что называется асинхронным в этой задаче, будет выполнено, и основная задача будет ожидать завершения вызовов асинхронного метода.
private static object lockObj = new object();
public static void Method()
{
lock (lockObj)
{
Task t = new Task(async()=>{ await Method2(lockObj); });
t.Start();
t.Wait();
}
}
Неуниверсальный конструктор Task нельзя использовать с делегатом async. Если да, то поведение будет не таким, как вы ожидаете. Подробнее см. в этом вопросе: Конструктор задач и Task.Run с асинхронным действием — разное поведение.
Спасибо, я делаю электронную почту обязательной, а также блокирую ветку с помощью решения @freeman. Оба (уникальное ограничение и блокировка потока)