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

Как долго будет блокироваться блокировка С#, и что произойдет, если код сработает во время блокировки?

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

Итак, код:

// class variable
private static object syncRoot = new object();

// in a method:
lock (syncRoot)
{
    DoIt();
}

Когда другой поток приходит и хочет выполнить код, как долго он будет ждать, пока не будет снята блокировка? Навсегда, или вы можете как-то установить тайм-аут?

И второе: если метод DoIt() вызывает исключение, блокировка все еще снята?

4b9b3361

Ответ 1

  Когда другой поток приходит и хочет выполнить код, как долго он будет ждать, пока не будет снята блокировка?

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

Вы можете установить тайм-аут?

Если вам нужно указать время ожидания, используйте Monitor.TryEnter как в

if(Monitor.TryEnter(obj, new TimeSpan(0, 0, 1))) {
    try {
        body 
    }
    finally {
        Monitor.Exit(obj);
    }
}

если метод DoIt() выдает исключение, блокировка все еще снята?

Да, lock(obj) { body } переводится на:

bool lockWasTaken = false;
var temp = obj;
try { Monitor.Enter(temp, ref lockWasTaken); { body } }
finally { if (lockWasTaken) Monitor.Exit(temp); }

Подробную информацию о том, что может произойти при возникновении исключения, см. в разделе Блокировки и исключения не смешиваются.

Ответ 2

Как уже упоминалось, регулярная блокировка будет ждать всегда, что представляет собой риск блокировок.

Предпочтительным механизмом является (и обратите внимание на ref):

bool lockTaken = false;
try {
    Monitor.TryEnter(lockObj, timeout, ref lockTaken);
    if(!lockTaken) throw new TimeoutException(); // or compensate
    // work here...
} finally {
    if(lockTaken) Monitor.Exit(lockObj);
}

Это позволяет избежать риска не блокировать блокировку в некоторых краевых корпусах.

finally (который существует в любой разумной реализации) обеспечивает блокировку даже в условиях ошибки.

Ответ 3

Простой lock(syncRoot) будет ждать вечно.

Вы можете заменить его на

if (System.Threading.Monitor.TryEnter(syncRoot, 1000))
{
     try
     {
         DoIt();
     }
     finally
     {
         System.Threading.Monitor.Exit(syncRoot);
     }
}

Каждый поток должен обеспечивать исключающую блокировку.

Обратите внимание, что стандартный lock(syncRoot) {} переписан на Monitor.Enter(syncRoot), а try/finally

Ответ 4

Замок будет ждать вечно, как сказал Хенк. Исключение все равно будет разблокировано. Он реализован внутри с блоком try-finally.

Ответ 5

Вы должны сделать шаг назад и спросить себя, почему вы разрешаете исключение из метода, скажите, когда Monitor you Enter Exit ed. Если даже вероятность того, что DoIt() может вызвать исключение (и я бы сказал, что, если возможно, вы переписываете DoIt() так, чтобы это было DoNot), тогда у вас должен быть блок try/catch внутри оператора lock(), поэтому что вы можете обеспечить необходимую очистку.

Ответ 6

Знайте необходимые предпосылки для тупика. Всегда используйте Monitor vs Lock, чтобы избежать взаимоблокировок.

Условия

Ответ 7

Jason anwser отлично, вот способ обернуть его, делая вызов проще.

    private void LockWithTimeout(object p_oLock, int p_iTimeout, Action p_aAction)
    {
        Exception eLockException = null;
        bool bLockWasTaken = false;
        try
        {
            Monitor.TryEnter(p_oLock, p_iTimeout, ref bLockWasTaken);
            if (bLockWasTaken)
                p_aAction();
            else
                throw new Exception("Timeout exceeded, unable to lock.");
        }
        catch (Exception ex)
        {
            // conserver l'exception
            eLockException = ex;
        }
        finally 
        { 
            // release le lock
            if (bLockWasTaken) 
                Monitor.Exit(p_oLock); 

            // relancer l'exception
            if (eLockException != null)
                throw new Exception("An exception occured during the lock proces.", eLockException);
        }
    }

а затем используйте его следующим образом:

        // ajouter à la liste des fiches à loader
        LockWithTimeout(m_lLoadingQueue, 3600, () =>
        {
            m_lLoadingQueue.Add(p_efEcranFiche);
        });