Проверка, имеет ли текущий поток блокировку - PullRequest
17 голосов
/ 30 января 2009

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

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. Единственный раз, когда он потерпит неудачу, это если другой поток владеет блокировкой и время ожидания вызова истекло.

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

Ответы [ 11 ]

12 голосов
/ 20 августа 2013

Это поддерживается в .NET 4.5 с использованием Monitor.IsEntered (объект) .

5 голосов
/ 02 февраля 2009

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

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

3 голосов
/ 30 января 2009

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

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

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

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

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

2 голосов
/ 08 июня 2011

Но, похоже, такого метода не существует. 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

1 голос
/ 08 декабря 2010

См. Мой ответ на аналогичный вопрос: Проверить блокировку без ее приобретения?

1 голос
/ 30 января 2009

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

Alex.

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

0 голосов
/ 15 июля 2014

Если вам это нужно в .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
}
}

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

Другая причина для обертки - избегать того, чтобы кто-то проскальзывал в вызовах _lock.EnterReadLock. В зависимости от использования класса они могут быть полезны, но удивительное количество кодеров не понимают блокировки чтения-записи.

0 голосов
/ 01 июля 2012

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

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 () {} в терминах стиля.

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

0 голосов
/ 03 февраля 2009

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

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

0 голосов
/ 30 января 2009

Не самая лучшая вещь, но ...

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")
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...