如果锁定对象内部发生异常,该锁定对象是否保持锁定状态?

在一个 c # 线程应用程序中,如果我要锁定一个对象,比方说一个队列,如果发生异常,对象会保持锁定状态吗?下面是伪代码:

int ii;
lock(MyQueue)
{
MyClass LclClass = (MyClass)MyQueue.Dequeue();
try
{
ii = int.parse(LclClass.SomeString);
}
catch
{
MessageBox.Show("Error parsing string");
}
}

据我所知,catch 之后的代码不会执行-但是我一直在想锁是否会被释放。

45417 次浏览

First; have you considered TryParse?

in li;
if(int.TryParse(LclClass.SomeString, out li)) {
// li is now assigned
} else {
// input string is dodgy
}

The lock will be released for 2 reasons; first, lock is essentially:

Monitor.Enter(lockObj);
try {
// ...
} finally {
Monitor.Exit(lockObj);
}

Second; you catch and don't re-throw the inner exception, so the lock never actually sees an exception. Of course, you are holding the lock for the duration of a MessageBox, which might be a problem.

So it will be released in all but the most fatal catastrophic unrecoverable exceptions.

Just to add a little to Marc's excellent answer.

Situations like this are the very reason for the existence of the lock keyword. It helps developers make sure the lock is released in the finally block.

If you're forced to use Monitor.Enter/Exit e.g. to support a timeout, you must make sure to place the call to Monitor.Exit in the finally block to ensure proper release of the lock in case of an exception.

"A lock statement is compiled to a call to Monitor.Enter, and then a try…finally block. In the finally block, Monitor.Exit is called.

The JIT code generation for both x86 and x64 ensures that a thread abort cannot occur between a Monitor.Enter call and a try block that immediately follows it."

Taken from: This site

yes, that will release properly; lock acts as try/finally, with the Monitor.Exit(myLock) in the finally, so no matter how you exit it will be released. As a side-note, catch(... e) {throw e;} is best avoided, as that damages the stack-trace on e; it is better not to catch it at all, or alternatively: use throw; rather than throw e; which does a re-throw.

If you really want to know, a lock in C#4 / .NET 4 is:

{
bool haveLock = false;
try {
Monitor.Enter(myLock, ref haveLock);
} finally {
if(haveLock) Monitor.Exit(myLock);
}
}

Your lock will be released properly. A lock acts like this:

try {
Monitor.Enter(myLock);
// ...
} finally {
Monitor.Exit(myLock);
}

And finally blocks are guaranteed to execute, no matter how you leave the try block.

I note that no one has mentioned in their answers to this old question that releasing a lock upon an exception is an incredibly dangerous thing to do. Yes, lock statements in C# have "finally" semantics; when control exits the lock normally or abnormally, the lock is released. You're all talking about this like it is a good thing, but it is a bad thing! The right thing to do if you have a locked region that throws an unhandled exception is to terminate the diseased process immediately before it destroys more user data, not free the lock and keep on going.

Look at it this way: suppose you have a bathroom with a lock on the door and a line of people waiting outside. A bomb in the bathroom goes off, killing the person in there. Your question is "in that situation will the lock be automatically unlocked so the next person can get into the bathroom?" Yes, it will. That is not a good thing. A bomb just went off in there and killed someone! The plumbing is probably destroyed, the house is no longer structurally sound, and there might be another bomb in there. The right thing to do is get everyone out as quickly as possible and demolish the entire house.

I mean, think it through: if you locked a region of code in order to read from a data structure without it being mutated on another thread, and something in that data structure threw an exception, odds are good that it is because the data structure is corrupt. User data is now messed up; you don't want to try to save user data at this point because you are then saving corrupt data. Just terminate the process.

If you locked a region of code in order to perform a mutation without another thread reading the state at the same time, and the mutation throws, then if the data was not corrupt before, it sure is now. Which is exactly the scenario that the lock is supposed to protect against. Now code that is waiting to read that state will immediately be given access to corrupt state, and probably itself crash. Again, the right thing to do is to terminate the process.

No matter how you slice it, an exception inside a lock is bad news. The right question to ask is not "will my lock be cleaned up in the event of an exception?" The right question to ask is "how do I ensure that there is never an exception inside a lock? And if there is, then how do I structure my program so that mutations are rolled back to previous good states?"