Подтвердить что ты не робот

Как заблокировать целое число в С#?

Есть ли способ заблокировать целое число в С#? Целые числа нельзя использовать с блокировкой, потому что они помещаются в бокс (и блокируют только блокировки ссылок).

Сценарий выглядит следующим образом: у меня есть сайт на базе форума с функцией модерации. То, что я хочу сделать, это убедиться, что не более одного модератора может умерить сообщение в любой момент времени. Для этого я хочу заблокировать идентификатор сообщения.

У меня было несколько идей до сих пор (например, с использованием словаря < int, object > ), но я ищу лучший и более чистый способ.

Любые предложения?

4b9b3361

Ответ 1

Мне нравится делать это вот так

public class Synchronizer {
    private Dictionary<int, object> locks;
    private object myLock;

    public Synchronizer() {
        locks = new Dictionary<int, object>();
        myLock = new object();
    }

    public object this[int index] {
        get {
            lock (myLock) {
                object result;
                if (locks.TryGetValue(index, out result))
                    return result;

                result = new object();
                locks[index] = result;
                return result;
            }
        }
    }
}

Затем для блокировки int вы просто (используя один и тот же синхронизатор каждый раз)

lock (sync[15]) { ... }

Этот класс возвращает тот же объект блокировки, когда ему присваивается тот же самый индекс дважды. Когда приходит новый индекс, он создает объект, возвращает его и сохраняет его в словаре в следующие моменты времени.

Его можно легко изменить для работы в целом с любым типом struct или значением или быть static, чтобы объект синхронизатора не нужно было передавать.

Ответ 2

Если это веб-сайт, то использование блокировки в процессе, вероятно, не самый лучший подход, как если бы вам нужно было масштабировать сайт на нескольких серверах или добавить другой сайт, на котором размещен API (или что-нибудь еще, что потребует другого процесса доступ к тем же данным для существования), тогда все ваши стратегии блокировки немедленно неэффективны.

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

Ответ 3

Я прочитал много комментариев, отметив, что блокировка небезопасна для веб-приложений, но, кроме веб-ферм, я не видел никаких объяснений, почему. Мне было бы интересно услышать аргументы против него.

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

Вот переделанная версия того, что отправил @Configurator. Он включает в себя пару функций, которые @Configurator не включал:

  • Unlocking: обеспечивает, чтобы список не становился необоснованно большим (у нас есть миллионы фотографий, и у нас может быть много разных размеров для каждого).
  • Общий: разрешает блокировку на основе разных типов данных (например, int или string).

Здесь код...

/// <summary>
/// Provides a way to lock a resource based on a value (such as an ID or path).
/// </summary>
public class Synchronizer<T>
{

    private Dictionary<T, SyncLock> mLocks = new Dictionary<T, SyncLock>();
    private object mLock = new object();

    /// <summary>
    /// Returns an object that can be used in a lock statement. Ex: lock(MySync.Lock(MyValue)) { ... }
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public SyncLock Lock(T value)
    {
        lock (mLock)
        {
            SyncLock theLock;
            if (mLocks.TryGetValue(value, out theLock))
                return theLock;

            theLock = new SyncLock(value, this);
            mLocks.Add(value, theLock);
            return theLock;
        }
    }

    /// <summary>
    /// Unlocks the object. Called from Lock.Dispose.
    /// </summary>
    /// <param name="theLock"></param>
    public void Unlock(SyncLock theLock)
    {
        mLocks.Remove(theLock.Value);
    }

    /// <summary>
    /// Represents a lock for the Synchronizer class.
    /// </summary>
    public class SyncLock
        : IDisposable
    {

        /// <summary>
        /// This class should only be instantiated from the Synchronizer class.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="sync"></param>
        internal SyncLock(T value, Synchronizer<T> sync)
        {
            Value = value;
            Sync = sync;
        }

        /// <summary>
        /// Makes sure the lock is removed.
        /// </summary>
        public void Dispose()
        {
            Sync.Unlock(this);
        }

        /// <summary>
        /// Gets the value that this lock is based on.
        /// </summary>
        public T Value { get; private set; }

        /// <summary>
        /// Gets the synchronizer this lock was created from.
        /// </summary>
        private Synchronizer<T> Sync { get; set; }

    }

}

Вот как вы можете его использовать...

public static readonly Synchronizer<int> sPostSync = new Synchronizer<int>();
....
using(var theLock = sPostSync.Lock(myID))
lock (theLock)
{
    ...
}

Ответ 4

Я лично пошел бы с помощью Greg или подхода Konrad.

Если вы действительно хотите lock от самого идентификатора сообщения (и считая, что ваш код будет работать только в одном процессе), то что-то вроде этого не слишком грязно:

public class ModeratorUtils
{
    private static readonly HashSet<int> _LockedPosts = new HashSet<int>();

    public void ModeratePost(int postId)
    {
        bool lockedByMe = false;
        try
        {
            lock (_LockedPosts)
            {
                lockedByMe = _LockedPosts.Add(postId);
            }

            if (lockedByMe)
            {
                // do your editing
            }
            else
            {
                // sorry, can't edit at this time
            }
        }
        finally
        {
            if (lockedByMe)
            {
                lock (_LockedPosts)
                {
                    _LockedPosts.Remove(postId);
                }
            }
        }
    }
}

Ответ 5

Этот параметр основывается на хорошем ответе, предоставленном конфигуратором, со следующими изменениями:

  • Предотвращает неуклонный рост размера словаря. Поскольку новые сообщения будут получать новые идентификаторы, ваш словарь блокировок будет расти на неопределенный срок. Решение заключается в модификации id с максимальным размером словаря. Это означает, что некоторые идентификаторы будут иметь одинаковую блокировку (и должны ждать, когда в противном случае они не будут), но это будет приемлемо для некоторого размера словаря.
  • Использует ConcurrentDictionary, поэтому нет необходимости в отдельной блокировке словаря.

Код:

internal class IdLock
{
    internal int LockDictionarySize
    {
        get { return m_lockDictionarySize; }
    }
    const int m_lockDictionarySize = 1000;
    ConcurrentDictionary<int, object> m_locks = new ConcurrentDictionary<int, object>();
    internal object this[ int id ]
    {
        get
        {
            object lockObject = new object();
            int mapValue = id % m_lockDictionarySize;
            lockObject = m_locks.GetOrAdd( mapValue, lockObject );
            return lockObject;
        }
    }
}

Кроме того, для полноты, существует альтернатива интернирования строк: -

  • Измените идентификатор на максимальное количество внутренних строк, которые вы разрешите.
  • Преобразовать это модифицированное значение в строку.
  • Объединить измененную строку с именем GUID или именем пространства имен для безопасности столкновений имен.
  • Запустите эту строку.
  • блокировка интернированной строки. См. этот ответ для получения некоторой информации:

Единственное преимущество подхода интернационализации строк заключается в том, что вам не нужно управлять словарем. Я предпочитаю словарь подхода к блокировкам, поскольку интернационалист делает много предположений о том, как работает интернационализация строк, и что он будет продолжать работать таким образом. Он также использует интернирование для чего-то, чего он никогда не имел в виду/предназначенного для этого.

Ответ 6

Почему бы вам не заблокировать всю публикацию вместо этого только по его идентификатору?

Ответ 7

Вам нужен совершенно другой подход.

Помните, что на веб-сайте у вас фактически нет действующего приложения на другой стороне, которое отвечает на то, что делает пользователь.

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

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

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

Ответ 9

Я сомневаюсь, что вы должны использовать функцию базы данных или уровня O/S, такую ​​как блокировки для решения бизнес-уровня. Замки несут значительные накладные расходы, когда они удерживаются в течение длительного времени (и в этих контекстах все, что находится за пару сотен миллисекунд, является вечностью).

Добавить поле статуса в сообщение. Если вы имеете дело с несколькими therads напрямую, вы можете использовать блокировки уровня O/S - установить флаг.

Ответ 10

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

Ответ 11

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

Ответ 12

Блокировка С# предназначена для обеспечения безопасности потоков и не работает так, как вы хотите, для веб-приложений.

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

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

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

Ответ 13

Вы хотите удостовериться, что удаление не происходит дважды?

CREATE PROCEDURE RemovePost( @postID int )
AS
    if exists(select postID from Posts where postID = @postID)
    BEGIN
        DELETE FROM Posts where postID = @postID
        -- Do other stuff
    END

Это довольно синтаксис SQL-сервера, я не знаком с MyISAM. Но это позволяет хранить хранимые процедуры. Я предполагаю, что вы можете макетировать аналогичную процедуру.

Во всяком случае, это будет работать в большинстве случаев. Единственный раз, когда он сработает, - это то, что два модератора отправляются почти в одно и то же время, а функция exists() передает один запрос непосредственно перед выполнением оператора DELETE по другому запросу. Я бы с радостью использовал это для небольшого сайта. Вы можете сделать еще один шаг и проверить, что удаление фактически удалило строку, прежде чем продолжить с остальными, что гарантировало бы атомарность всего этого.

Пытаясь создать блокировку в коде, для этого варианта использования, я считаю очень непрактичным. Вы ничего не теряете, если у вас есть два модератора, пытающихся удалить сообщение, с одним последующим, а другое не имеет эффекта.

Ответ 14

Вы должны использовать объект синхронизации следующим образом:

public class YourForm
{
    private static object syncObject = new object();

    public void Moderate()
    {
        lock(syncObject)
        {
            // do your business
        }
    }
}

Но этот подход не должен использоваться в сценарии веб-приложений.