Как долго будет блокироваться блокировка С#, и что произойдет, если код сработает во время блокировки?
Я видел следующий код и хотел использовать его для простого действия, которое может выполняться только по одному за раз, и не будет происходить часто (поэтому вероятность появления дважды за раз очень мала, но вы никогда не знаете).
Итак, код:
// class variable
private static object syncRoot = new object();
// in a method:
lock (syncRoot)
{
DoIt();
}
Когда другой поток приходит и хочет выполнить код, как долго он будет ждать, пока не будет снята блокировка? Навсегда, или вы можете как-то установить тайм-аут?
И второе: если метод DoIt()
вызывает исключение, блокировка все еще снята?
Ответы
Ответ 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);
});