У нас есть устаревшая среда ASP.NET 2.0, где каждое исполнение страницы аутентифицируется определенному пользователю, и поэтому у меня есть целое число, представляющее введенный в систему идентификатор пользователя.
На одной из страниц мне нужно запустить некоторый код, где я хочу, чтобы пользователь не выполнял дублирующее действие. Найти это сложно, чтобы гарантировать, что этого не может быть, даже если мы делаем базовую проверку защиты от дублирования.
Очевидно, я мог бы создать статический объект и сделать lock(myObject) {... }
вокруг всего фрагмента кода, чтобы попытаться предотвратить некоторые из этих условий гонки. Но я не хочу создавать узкое место для всех... просто хочу, чтобы один и тот же зарегистрированный пользователь запускал код одновременно или почти одновременно.
Поэтому я собираюсь создать экземпляр объекта для каждого пользователя и сохранить его в кеше на основе их идентификатора пользователя. Затем я просматриваю этот объект, и если объект найден, я блокирую его. Если не найден, я сначала создаю/кеширую его, а затем блокирую на нем.
Имеет ли это смысл? Есть ли лучший способ добиться того, что мне нужно?
Что-то вроде этого - вот что я думаю:
public class MyClass
{
private static object lockObject = new object(); // global locking object
public void Page_Load()
{
string cachekey = "preventdupes:" + UserId.ToString();
object userSpecificLock = null;
// This part would synchronize among all requests, but should be quick
// as it is simply trying to find out if a user-specific lock object
// exists, and if so, it gets it. Otherwise, it creates and stores it.
lock (lockObject)
{
userSpecificLock = HttpRuntime.Cache.Get(cachekey);
if (userSpecificLock == null)
{
userSpecificLock = new object();
// Cache the locking object on a sliding 30 minute window
HttpRuntime.Cache.Add(cachekey, userSpecificLock, null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
new TimeSpan(0, 30, 0),
System.Web.Caching.CacheItemPriority.AboveNormal, null);
}
}
// Now we have obtained an instance of an object specific to the user,
// and we'll lock the next block of code specifically to them.
lock (userSpecificLock)
{
try
{
// Perform some operations to check our database to see if the
// transaction already occurred for this user, and if not,
// perform the transaction, and then record it into our db.
}
catch (Exception)
{
// Rollback anything our code has done up until this exception,
// so that if the user tries again, it will work.
}
}
}
}
Решение состоит в использовании мьютекса.
Mutex можно назвать, так что вы можете назвать свой идентификатор пользователя, и они работают для полного компьютера, поэтому они работают, если у вас много процессов в одном и том же пуле (веб-сад).
Подробнее читайте:
http://en.wikipedia.org/wiki/Mutual_exclusion
Asp.Net. Доступ к синхронизации (мьютекс)
http://www.dotnetperls.com/mutex
MSute Mutex с примером
lock
Блокировка работает только внутри одного и того же родительского потока, и вы можете использовать их только для синхронизированных статических переменных. Но также HttpRuntime.Cache
представляет собой статическую память, то есть означает, что если у вас много процессов под одним и тем же пулом (веб-сад), у вас много разных переменных кэша.
Страница также автоматически синхронизируется сеансом. Поэтому, если вы отключили сеанс для этой страницы, то в мьютексе есть точка, если нет, все сеанс сессии блокирует page_load (с мьютексом), а мьютекс, который вы собираетесь разместить, не имеет никакого значения.
Некоторая ссылка о:
Сервер ASP.NET не обрабатывает страницы асинхронно
Является ли Session переменной потокобезопасной в цикле Parallel.For на странице ASP.Net
HttpContext.Current имеет значение null, когда в методе, вызванном из PageAsyncTask
ConcurrentDictionary
UserId
дляLock object
парLock object
вместо прямого измененияHttpRuntime.Cache
.