The BeginXXX/EndXXX asynchronous invocation pattern in .NET has BeginXXX return an IAsyncResult. In the past, I’ve not normally done anything with this, rather preferring to use the one passed into the callback e.g.
var cmd = new SqlCommand();
// Initialise the command...
cmd.BeginExecuteReader(iar =>
{
try
{
using (var rdr = cmd.EndExecuteReader(iar))
{
// Deal with the results...
}
}
catch(Exception e)
{
// Deal with the error...
}
}, null);
Surprisingly, this code has a subtle leak (of sorts). And it’s to do with a property of the IAsyncResult that’s not even used, the AsyncWaitHandle. Now this is normally only used if you really need to block to wait for the outcome of the operation, e.g.
var conn = new SqlConnection(connstr);
var cmd = new SqlCommand();
// Initialise the command...
var iar = cmd.BeginExecuteReader();
iar.AsyncWaitHandle.WaitOne();
try
{
using (var rdr = cmd.EndExecuteReader(iar))
{
// Deal with the results...
}
}
catch(Exception e)
{
// Deal with the error...
}
The WaitHandle is really just a thin veneer a native Windows HANDLE, so when is it actually created and subsequently cleaned up? Well, unfortunately, it depends. The documentation advises implementers of IAsyncResult of two things
- The object that implements IAsyncResult does not need to create the WaitHandle until the AsyncWaitHandle property is read.
- (the) AsyncWaitHandle should be kept alive until the user calls the method that concludes the asynchronous operation. At that time the object behind AsyncWaitHandle can be discarded.
The italics are mine; what this means in practice is that you have to determine what your exact flavour of BeginXXX/EndXXX actually returns.
For example, the System.Data.SqlClient async operations return an IAsyncResult that both
- Eagerly creates the WaitHandle (regardless of whether you ever use it)
- Doesn’t actually dispose of the WaitHandle when EndXXX is called.
This means that, even if you use the first of the above two usage patterns and never explicitly reference the WaitHandle, at completion of the operation, the handle remains open until collected. But this could be a while, especially if the IAsyncResult was suspended long enough to get promoted through a GC generation or two. And if you hammer your database sufficiently, you’ll see a significant spike in the number of open handles in your process. You also incur the overhead of having the WaitHandle itself placed on the finalization queue.
In order to follow Microsoft’s own advice about eagerly disposing of unmanaged resources, we need to amend the first sample:
cmd.BeginExecuteReader(iar =>
{
using (iar.AsyncWaitHandle)
{
try
{
using (var rdr = cmd.EndExecuteReader(iar))
{
// Deal with the results...
}
}
catch (Exception e)
{
// Deal with the error...
}
}
}, null);
Extension methods can be used to reduce clutter and increase modularity:
public static class SqlCommandExtensions
{
public static void BeginSafeExecuteReader(this SqlCommand cmd, AsyncCallback callback)
{
cmd.BeginExecuteReader(iar =>
{
using (iar.AsyncWaitHandle)
{
callback(iar);
}
}, null);
}
}
Which allows a solution very close to the original, but with deterministic disposal of the WaitHandle:
cmd.BeginSafeExecuteReader(iar =>
{
try
{
using (var rdr = cmd.EndExecuteReader(iar))
{
// Deal with the results...
}
}
catch (Exception e)
{
// Deal with the error...
}
});
(By the way, the WaitHandle in the IAsyncResult returned from methods in the System.Net namespace is both lazily created and cleaned up on EndXXX, which means none of the above is necessary for async operations in that particular namespace).