Introduction
C# is a safe and managed language. By safe, we can understand it will help developers avoid common errors, like causing memory leaks or accessing invalid memory. In fact, .NET is really good at this, but the Garbage Collection does not occur immediately so, when we need to free a resource immediately, we must call some type of "free" method, like Close in files and database connections, Commit or Rollback transactions or, in general, the Dispose()
method, which is also implemented by files, database objects and transactions.
Dispose()
methods free the associated resources immediately, so a file being written can now be read by another process, a database connection can return to the pool and other unmanaged resources (like windows Handles) are freed immediately, releasing memory.
But, how do we call Dispose()
?
In C#, we have the using
keyword which must be used as follows:
using(var disposable = new DisposableType())
{
... do what's needed with the disposable variable here ...
}
And, at the end of the block, the Dispose
will be called. Such code will be compiled like:
{
var disposable = new DisposableType();
try
{
... do what's needed with the disposable variable here ...
}
finally
{
if (disposable != null)
disposable.Dispose();
}
}
Even the new
keyword will never return null
, the "pattern" includes the if (disposable != null)
, but I really think the JIT will optimize and remove such unnecessary if
.
So, this code is safe, right? Any exception after the disposable object is created will be protected by the finally
clause and will call Dispose
.
Well, no. For synchronous exceptions, that's correct, but there are asynchronous exceptions too, in special ThreadAbortException
.
Imagine that just after setting the disposable value and before the try
, a ThreadAbortException
is thrown, by a request to Abort
from another thread. We are not yet in the try
, so the finally will not be called. This is an issue. It will not cause a memory leak, considering that GC will eventually collect the object, but such resource will be held for a long time. If this is a database connection, it could not return to the pool. If this is a file, it can be kept in exclusive mode forbidding anyone else from using it.
So, how do we solve this? I will present the solution later, but I will first show something that looks like a solution. Why? Because I think that not knowing the problem with this pseudo solution will make someone try to use it, specially because there are some places that already use this structure as the "right" one.
The Code
DisposableType disposable = null;
try
{
disposable = new DisposableType();
... use the disposable object here ...
}
finally
{
if (disposable != null)
disposable.Dispose();
}
In this solution, the disposable is initialized with null
. So, the block is protected with a try
/finally
before the disposable object is created. If the ThreadAbortException
comes before object is created, the if
in the finally
will make it work. If the ThreadAbortException
comes just after the object is created, it will work also. But, there is still a problem.
Abort can happen at any assembly instruction. Even our line looks like disposable = new DisposableType()
, in assembler we first allocate the type and then we store such result in disposable variable. To make it worse, constructors can also be interrupted in the middle (I made many tests myself, even not having examples to show exactly where the exceptions happen).
So, is there a possible way to solve the problem? Yes. But we must use it with caution. As already shown, when an exception is thrown the finally
block gets executed. If we are already in the finally
block, it continues to execute normally, so an Abort
called for a thread that is already in finally
block does not force it to exit to another finally
block, missing some steps. So, we can use this, we put all the code that should not be blocked inside a finally
block.
But, remember, use it with caution. If you use any blocking operation in such block, you will not be able to Abort
the thread even if you need to. This can be a very frustrating user experience.
So, let's see the code:
DisposableType disposable = null;
try
{
try
{
}
finally
{
disposable = new DisposableType();
}
... use the disposable object here ...
}
finally
{
if (disposable != null)
disposable.Dispose();
}
With this solution, or the Abort
happens before the DisposableType
is allocated, or after it is fully allocated and the variable is set. No "in-the-middle" aborts.
So, this is it? Well, for ThreadAbortExceptions
, yes. For other asynchronous exceptions, no. If you look at the documentation of CERs (Constrained Execution Regions), be prepared to deal with the ThreadAbortException
is only one of the needed cautions. The system can run out-of-memory when it needs to compile a method or the application can be asked to shutdown abruptly, avoiding normal finally
clauses. But, don't think this makes such a technique obsolete. ThreadAbortExceptions
are much more common than the other exceptions and, specially when the application is shutdown abruptly, files or database connections left open will be reclaimed by the operating system either way.
Improvements
The technique presented works, but it is ugly. So, I decided to create some classes and helper methods. The most important one is in the AbortSafe
class, and is the Run
method that receives 3 parameters. Let's look at the method:
public static void Run(Action allocationBlock, Action codeBlock, Action finallyBlock)
{
if (allocationBlock == null)
throw new ArgumentNullException("allocationBlock");
if (codeBlock == null)
throw new ArgumentNullException("codeBlock");
if (finallyBlock == null)
throw new ArgumentNullException("finallyBlock");
try
{
try
{
}
finally
{
allocationBlock();
}
codeBlock();
}
finally
{
finallyBlock();
}
}
It simply receives three actions. If the allocation starts, it is guaranteed to finish, even if the abort happens in the middle. Independent from the success of allocation, finalization will be run. The only block that is abortable is the code block.
Let's look a simple example of how to use it:
DisposableType disposable = null;
AbortSafe.Run
(
() => disposable = new DisposableType(),
() =>
{
... do what you need with the disposable object...
},
() => disposable.CheckedDispose()
);
The CheckedDispose
is an extension method found in Pfz.Extensions.DisposeExtensions
namespace. It will simply check if the variable is not null
before disposing it. I did this only to avoid creating a new code block to do the "if
". As you can see, the code is "less" ugly than creating an empty try
to program in the finally
block. Also, it does not look like an error, so it does not have the same chance of being "corrected" by someone else that does not understand why the code was written in a finally
clause.
Sample
In the attached zip is a program that creates and aborts threads, which will be creating and recreating the same file, but allowing you to choose the way it will do this:
- With the
using
keyword
- With the pseudo-solution
- With the
AbortSafe
solution
The 1st and 2nd, at some time, will cause an IO exception because the file is "already opened", while the 3rd will not cause such exception.