Предотвращение дублирования пользовательских транзакций с помощью пользовательских блокировок?

У нас есть устаревшая среда 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.
            }
        }
    }
}

person Mason G. Zhwiti    schedule 19.08.2014    source источник
comment
Возможно, рассмотрите возможность использования статического ConcurrentDictionary UserId в пары Lock object вместо прямого изменения HttpRuntime.Cache.   -  person voithos    schedule 19.08.2014


Ответы (1)


Решение состоит в использовании mutex< /а>.

Mutex можно назвать, поэтому вы можете назвать свой идентификатор пользователя, и они работают для всего компьютера, поэтому они работают, если у вас есть много процессов в одном пуле (веб-сад).

Еще почитать:
http://en.wikipedia.org/wiki/Mutual_exclusion
Asp.Net. Доступ к синхронизации (мьютекс)
http://www.dotnetperls.com/mutex
MSDN Mutex с примером

Некоторые моменты

Блокировка lock работает только внутри одного и того же и родительского потоков, и вы можете использовать их только для синхронизированных статических переменных. Но также HttpRuntime.Cache является статической памятью, это означает, что если у вас есть много процессов в одном пуле (веб-сад), у вас есть много разных переменных кэша.

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

Некоторая ссылка на:
ASP.NET Server делает не обрабатывать страницы асинхронно
Является ли переменная сеанса потокобезопасной в цикле Parallel.For на странице ASP.Net
HttpContext.Current имеет значение null, когда метод вызывается из PageAsyncTask

person Aristos    schedule 19.08.2014
comment
Объект Mutex, очень интересный. Вы также только что поразили меня, заявив, что ASP.NET блокирует Page_Load с помощью Session. Я никогда этого не осознавал, а я работаю с ASP.NET уже 10 лет. Угу. - person Mason G. Zhwiti; 20.08.2014