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

Проверка того, владеет ли текущий поток блокировкой

Предположим, что у меня есть следующий код:

public class SomeClass()
{
    private readonly object _lock = new object();

    public void SomeMethodA()
    {
        lock (_lock)
        {
            SomeHelperMethod();

            //do something that requires lock on _lock
        }
    }

    public void SomeMethodB()
    {
        lock (_lock)
        {
            SomeHelperMethod();

            //do something that requires lock on _lock
        }
    }

    private void SomeHelperMethod()
    {
        lock (_lock)
        {
            //do something that requires lock on _lock
        }
    }
}

Блокировка внутри SomeHelperMethod кажется избыточной и расточительной, поскольку все вызывающие абоненты уже сделали блокировку. Однако простое удаление блокировки из SomeHelperMethod кажется опасным, поскольку мы могли бы позже реорганизовать код и забыть заблокировать объект _lock до вызова SomeHelperMethod.

В идеале я мог бы обойти это, утверждая, что текущий поток владеет блокировкой на _lock внутри SomeHelperMethod:

private void SomeHelperMethod()
{
    Debug.Assert(Monitor.HasLock(_lock));

    //do something that requires lock on _lock
}

Но такой метод не существует. Monitor.TryEnter не помогает, поскольку блокировки являются повторными. Поэтому, если текущий поток уже владеет блокировкой, TryEnter все равно будет успешным и вернет true. Единственный раз, когда это произойдет, - если другой поток владеет блокировкой и время вызова.

Итак, существует ли такой метод? Если нет, то почему? Мне это вообще не кажется опасным, поскольку он просто говорит вам, принадлежит ли текущий поток (не другой поток) блокировке или нет.

4b9b3361

Ответ 2

Блокировка или повторная блокировка фактически свободны. Недостатком этой низкой стоимости является то, что у вас не так много функций, как другие механизмы синхронизации. Я бы просто блокировал всякий раз, когда блокировка жизненно важна, как вы уже выше.

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

Ответ 3

Есть фундаментальная проблема с вашим запросом. Предположим, что существует метод IsLockHeld(), и вы будете использовать его следующим образом:

if (not IsLockHeld(someLockObj)) then DoSomething()

Это не может работать по дизайну. Ваш поток может быть предварительно убран между оператором if() и вызовом метода. Разрешить запуск другого потока и блокировку объекта. Теперь DoSomething() будет запущен, даже если блокировка сохранена.

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

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

Ответ 4

Но такой метод не существует. Monitor.TryEnter не помогает, потому что блокировки возвращаются. Поэтому, если текущий поток уже владеет блокировкой, TryEnter все равно будет успешным и вернет true. Единственный раз, когда это произойдет, - если другой поток владеет блокировкой и время вызова.

Я нашел обходное решение, которое работает подобно Monitor.TryEnter() и все же не имеет проблем с повторными входами. В основном вместо использования Monitor.TryEnter() вы можете использовать Monitor.Pulse(). Эта функция генерирует исключение SynchronizationLockException, если текущий поток не удерживает блокировку.

Пример:

try
{
    System.Threading.Monitor.Pulse(lockObj);
}
catch (System.Threading.SynchronizationLockException /*ex*/)
{
    // This lock is unlocked! run and hide ;)
}

Документы: http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse.aspx

Ответ 5

OK,  Теперь я понимаю вашу проблему.  Концептуально я сейчас думаю о объекте Семафора. Вы можете легко проверить значение семафора (значение больше или равно 0 означает, что ресурс доступен). Кроме того, это фактически не блокирует ресурс, который выполняется путем увеличения или уменьшения значения семафора.  Объект Semaphore присутствует в платформе .NET, но я не использовал его, поэтому не могу привести пример. При необходимости я могу создать простой пример и разместить его здесь. Дайте мне знать.

Алекс.

L.E.  Теперь я не уверен, если у вас есть контроль над механизмами синхронизации в приложении. Если у вас есть только доступ к объекту, который можно заблокировать или нет, мое решение может оказаться (еще раз) бесполезным.

Ответ 7

Я хотел бы, чтобы это было возможно, что сделало бы мой код намного, намного чище.

Ответ 8

У меня нет опыта программирования .NET, но в моем собственном коде (Delphi) я однажды применил такие утверждения, используя специальные методы Acquire() и Release() в критическом классе разделов, с частной переменной-членом для идентификатор потока, получившего cs. После EnterCriticalSection() было записано возвращаемое значение GetCurrentThreadID, а перед значением LeaveCriticalSection() значение поля было reset.

Но я не знаю, поддерживает ли .NET аналогичную вещь, или вы можете реализовать ее в своей программе...

Ответ 9

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

public void Method1()
{
    if(!lockHeld()) lock();
    //DO something
}
public void Method2()
{
    if(!lockHeld()) lock();
    //DO something
}
public void Method3()
{
    if(!lockHeld()) lock();

    //Do something

    unlock()
}

Итак, я написал себе класс для этого:

public class LockObject
{
    private Object internalLock;

    private bool isLocked;
    public bool IsLocked
    {
        get { lock (internalLock) return isLocked; }
        private set { lock (internalLock) isLocked = value; }
    }

    public LockObject()
    {
        internalLock = new object();
    }

    public void UsingLock(Action method)
    {
        try
        {
            Monitor.Enter(this);
            this.IsLocked = true;

            method();
        }
        finally
        {
            this.IsLocked = false;
            Monitor.Exit(this);
        }
    }
}

Тогда я могу использовать его как:

    public void Example()
    {
        var obj = new LockObject();

        obj.UsingLock(() =>
        {
            //Locked, and obj.IsLocked = true
        });
    }

Примечание. Я запретил методы Lock() Unlock() для этого класса, но довольно просто обертки для Monitor.Enter/Exit, которые устанавливают IsLocked в true. Пример просто иллюстрирует, что он очень похож на lock() {} в терминах стиля.

Может оказаться ненужным, но это полезно для меня.

Ответ 10

Если вам это нужно в .NET 4.5, см. принятый ответ от @Dave.

Однако, если вам это нужно в .NET 3.5 или 4, вы можете заменить использование блокировок монитора для ReaderWriterLockSlim следующим образом.

По словам Жозефа Альбахари, это примерно удвоит ваши (неоспоримые) блокировки накладных расходов, но все равно очень быстро (40%). (Отказ от ответственности, эта страница не указывает, были ли тесты проведены с использованием политики рекурсивного замка.)

public class SomeClass()
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

public void SomeMethodA()
{
    _lock.EnterWriteLock();
    try
    {
        SomeHelperMethod();

        //do something that requires lock on _lock
    }
    finally
    {
       _lock.ExitWriteLock();
    }
}

private void SomeHelperMethod()
{
    Debug.Assert(_lock.IsWriteLockHeld);

    //do something that requires lock on _lock
}
}

Если вы чувствуете, что это слишком неэффективно, вы можете, конечно, написать свой собственный класс-оболочку вокруг блокировок монитора. Тем не менее, в других сообщениях упоминается логический флаг, который вводит в заблуждение, поскольку вопрос не является "Является ли блокировка какой-либо нитью?" (что является глупым и опасным вопросом). Правильный вопрос: "Является ли блокировка этой ЭТОЙ нитью?". Однако не проблема, просто установите флажок ThreadLocal (для поддержки рекурсивной блокировки) и убедитесь, что обновления выполняются после Monitor.Enter и до Monitor.Exit. В качестве улучшения утверждают, что счетчик не опускается ниже 0 при "разблокировке", поскольку это указывает на неуравновешенные вызовы "Ввод/Выход".

Еще одна причина для оболочки - избежать того, чтобы кто-то проскользнул в вызовы _lock.EnterReadLock. В зависимости от использования класса они могут быть полезны, но удивительное количество кодировщиков не понимает Lock-Writer Locks.

Ответ 11

Не лучшее, что можно сделать, но...

bool OwnsLock(object someLockObj)
{
  if (Monitor.TryEnter(someLockObj))
  {
    Monitor.Exit();
    return false;
  }
  return true;
}

Debug.Assert(OwnsLock(_someLockObject), "_someLockObject should be locked when this method is called")