问题
This is a large multi-threaded project (which I didn't write) that I am fixing. The application hangs on some locks which I am tracking down.
I went through and replaced all the "lock" statements with Monitor.TryEnter
so I could set a wait period. I am occasionally getting an exception with the Monitor.Exit
.
The original style was
private List<myClass> _myVar= new List<myClass>();
if (_myVar != null)
{
lock (_myVar)
{
_myVar = newMyVar; // Where newMyVar is another List<myClass>
}
}
I replaced all my locks like above with:
if (_myVar != null)
{
bool lockTaken = false;
try
{
Monitor.TryEnter(_myVar, new TimeSpan(0, 0, 5), ref lockTaken);
if (lockTaken)
{
_myVar = newMyVar; // Where newMyVar is another List<myClass>
}
}
finally
{
if (lockTaken) Monitor.Exit(_myVar);
}
}
The exception I am getting is
SynchronizationLockException Object synchronization method was called from an unsynchronized block of code
. If this is true, why doesn't the original lock statement also throw an exception?
Would it be safe to put the Monitor.Exit
in a try catch and just ignore it if there is an exception?
回答1:
It should be very clear why you are getting the exception in your new code. If the lock is taken then the object that is unlocked is not the object that was locked. Locks take an object, not a variable. The correct translation of the deeply wrong original code is
// THIS CODE IS COMPLETELY WRONG; DO NOT USE IT
if (_myVar != null)
{
bool lockTaken = false;
var locker = _myVar;
try
{
Monitor.TryEnter(locker, new TimeSpan(0, 0, 5), ref lockTaken);
if (lockTaken)
{
_myVar = newMyVar; // where newMyVar is another List<myClass>
}
}
finally
{
if (lockTaken) Monitor.Exit(locker);
}
}
Which will not throw on exit, but is still completely wrong.
Never lock on the contents of a variable and then mutate the variable; every subsequent lock will lock on a different object! So you have no mutual exclusion.
And never lock on a public object! If that list leaks out anywhere then other wrong code can be locking on that list in an unexpected order, which means deadlocks -- which is the original symptom you are diagnosing.
The correct practice for locking on a field is to create a private readonly object field used only as a locker, and used every time the field is accessed. That way you know that (1) the field is always accessed under the same lock, no matter its value, and (2) the lock object is used only for locking that field, and not for locking something else. That ensures mutual exclusion and prevents deadlocks.
The fact that someone wrote a large multithreaded program without understanding the most basic facts about locks means that it is almost certainly a complete mess of hard-to-find bugs. The fact that this wasn't immediately obvious upon reading the code means that you don't have enough knowledge of threading to fix the problems correctly. You're going to need to either find an expert on this stuff who can help you, or gain at least a minimal working knowledge of correct practices.
I cannot emphasize enough that this is hard stuff. Programs with multiple threads of control in them are extremely difficult to write correctly on modern hardware; many of the things you believe are guaranteed by the language are only guaranteed in single threaded programs.
来源:https://stackoverflow.com/questions/46853665/exception-on-monitor-exit-in-c-sharp