The RAII idiom (Resource Acquisition Is Initialization) originates in C ++ and consists in the fact that some resource is captured in the object's constructor and released in its destructor. And since the destructor of local objects is automatically called when the method exits (or just out of scope), regardless of the cause (normal completion of the method or when an exception is thrown), using this idiom is the simplest and most effective way to write followed C ++ code safe from a point view of exceptions.
When moving to “managed” platforms, such as .NET or Java, this idiom in some way loses its relevance, since the garbage collector deals with the release of memory, namely memory was the most popular resource that had to be taken care of in C ++. However, since the garbage collector deals only with memory and does not contribute to the deterministic release of resources (such as operating system descriptors), the RAII idiom is still used in .NET and in Java, even if few developers know about this intricate name.
Starting with the first version of the C # language, we had at our disposal a
using construct that provided automatic release of resources by calling the
Dispose method. Another method of deterministic release of resources was (and remains) the manual use of a
try /
finally block. Let's look at the following couple of simple examples of using the
ReaderWriterLockSlim class, which is designed to more effectively share a common resource between “readers” and “writers”:

')
In the
ManualLockManagerment method, manual locking is used, while the
UsingBasedMethod method is based on a small shell. A full example of this shell can be found
here , but it’s not difficult to guess how it
works : the
UseReadLock extension
method creates some object, the constructor of which takes the lock for reading, and the
Dispose method frees it. The question is how much the two fragments are equivalent and what should be preferred? Of course, a “bicycle” with a
using block looks more readable, but is this the only difference between them?
OK. Let's complicate the first example a little. That if in our code there is the possibility of recursive calls, when the method after the lock is captured for reading, it calls the method that locks the read lock again. I don’t think that every reader remembers the behavior of the
ReaderWriterLockSlim object in terms of re-capture (default reentrancy mode), so I’ll immediately say that, unlike the
lock construct,
ReaderWriterLockSlim objects
do not support recursive captures by default:

In this case, we get the mismatched state of the lock object and the generation of the
SynchronizationLockException exception, but how obvious is exactly which point in the code will generate it, and what consequences will it cause? The problem with the above code is that it does not match the behavior of the RAII idiom and the
using block:
resources should be freed in the finally block only if they were successfully captured before .
In this case, the following occurs: since the
ReaderWriterLockSlim object
does not support recursive captures, when you try to call the
EnterReadLock method a second time (line 3 of the
AnotherMethod method), a
LockRecusionException exception will be generated, but since this call is inside the
try block, the
AnotherMethod method’s
finally block is called a subsequent call to
ExitReadLock . As a result, in line 4 we will get a free lock, which in itself is not healthy, because we did not capture it; after that, the control is returned to the
SomeMethod method and goes to the
finally block, where the
ExitReadLock method will be called again.
Deadlock and other troubles
This is where the fun begins. When I thought about the problem of using the construction
using vs manual resource management via
try /
finally , I assumed that this code would fall with the exception in the line for the first time, and then fall again in line 2 of the
SomeMethod method. I reasoned like this: since
ReaderWriterLockSlim does not support recursive captures, the original exception will occur in line 3, but since the lock will be released in line 4, then when you try to re-release the lock, the first method will generate another exception that will “mask” the original exception.
In such an artificial example, with this behavior, finding the real reason will be quite simple, but such behavior on the production server may make you feel good about the blood, because the code may not be so obvious, why does it say that the lock is not captured when it is necessarily captured in the beginning of the method.
However, in fact, the behavior will be somewhat different, or rather, it will be exactly like this in .NET 4.5, but it will be completely different in previous versions of the platform. Let's take it in order.
The problem is that on .NET 4.0 (and below) the behavior will be as follows: an attempt to call the
ExitReadLock method before calling the
EnterReadLock method results in an exception, but two
ExitReadLock calls after one
EnterReadLock call
succeed !

The most unpleasant thing in this matter is that in the
current old versions of the framework, this code will not just succeed, it will lead to a mismatch of the state of the lock object, with the result that we will see the following on the console:
“ ReadCount = 4294967295, ReadLockHeld = False ” . In fact, in line 3 we reduced the value of the lock counter to 0, and in line 4 we reduced it again; as a result, the counter became -1, and 4294967295 is just a representation of the value “-1” in an unsigned format. But most importantly,
any subsequent attempt at capturing a lock on a record will stick forever, since the dork will assume that the lock on reading is still captured.As it turned out, this is a known bug in the .NET Framework, which is finally fixed in .NET 4.5. After the VS2012 installation, we get quite expected, though not very pleasant behavior: the original exception that occurred when the lock was re-captured was masked by a new exception that occurs when trying to release an un-captured lock!
Use using or capture resources correctly!
Now let's remember how the
using block
works :

The
using construct is deployed in such a way that the resource is captured before the
try block, so that when an exception is thrown when it is captured, the release will not be performed. This behavior can lead to unpleasant consequences if the
using construct is combined with the object initializer (details in the note
Initializers of objects in the using block ), but it fully corresponds to the behavior of constructors / destructors in C ++, in which the destructor was called only if the object was successfully created .
NOTEHere we are confronted with another distinction between destructors in C ++ and a finalizer in C #. Unlike the destructor, the finalizer is called even if the constructor of the created object fell with an exception. This behavior is quite logical, as it simplifies the creation of a resource management code in the C # language, when the finalizer only needs to check the fact of successful resource capture (by checking for
null ,
IntPtr . Zero , etc.).
Since
using the
Dispose method is called only when the resource is successfully captured, the following code behaves as predictably as possible: the code calling the
SomeMethod method will get an exception that occurred in line 2 of the
AnotherMethod method when it tries to lock again, while the lock object is completely Normal condition in any version of the .NET Framework:
