FireAndForget uses a DelegateWrapper which calls InvokeWrappedDelegate, which in
turn calls the DynamicInvoke method of the wrapped delegate. It executes the
specified delegate with the specified arguments asynchronously on a thread pool
thread. EndWrapperInvoke calls EndInvoke on the wrapper and Close on the resulting
WaitHandle to prevent resource leaks. In other words, FireAndForget makes an
asynchronous call but it handles the async callback internally for you, and you
don't have to wait.
NOTE that this is not the same as using the BeginExecuteNonQuery method of the SqlCommand
object; that requires an async callback method which must be invoked to complete the operation. With FireAndForget, everything is handled internally. Think
of it like a coin toll booth where you can just throw your change at it and never
even have to slow down.
When would you use this? Anytime you have a high volume of requests or threads for
which you need to "do something" very fast in a non-blocking way, such
as updating a counter column in your table, sending out a quick email, and so
on. You don't want your page request "hung up" with a blocking call
waiting for a method to return, and this is one of the best ways to do this.
Having said that, this is not a "Band Aid" for bad database design. If
there is something wrong with your database and you're getting timeouts because
of page locking, resource contention or deadlocks, this isn't going to help you
- you need to fix your schema, SQL and code first, get it to work fast and
correctly. Then FireAndForget will make it even faster.
Here is the simplified FireAndForget static ThreadUtil class:
using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Security;
using System.Text;
using System.Threading;
using PAB.Data.Utils;
namespace FireAndForget
{
/// <summary>
/// Provides threadsafe, non-blocking methods to invoke methods using the Fire and
Forget pattern
/// <b>Usage:</b>
/// <example>FireAndForget.ThreadUtil.InvokeSproc(connectionString,sprocName,parameters)</example>
/// </summary>
public static class ThreadUtil
{
#region Delegates
public delegate void InvokeSprocDelegate(string connectionString, string sprocName, object[] parameters);
#endregion
/// <summary>
/// Callback used to call <code>EndInvoke</code> on the asynchronously
/// invoked DelegateWrapper.
/// </summary>
private static AsyncCallback callback = EndWrapperInvoke;
/// <summary>
/// An instance of DelegateWrapper which calls InvokeWrappedDelegate,
/// which in turn calls the DynamicInvoke method of the wrapped
/// delegate.
/// </summary>
private static DelegateWrapper wrapperInstance = new DelegateWrapper(InvokeWrappedDelegate);
public static void InvokeSproc(string connectionString, string sprocName, object[] parameters)
{
FireAndForget(new InvokeSprocDelegate(InvokeASproc),
new object[] { connectionString, sprocName, parameters });
}
private static void InvokeASproc (string connectionString, string sprocName, object[] parameters)
{
SqlHelper.ExecuteNonQuery(connectionString, sprocName, parameters);
}
/// <summary>
/// Executes the specified delegate with the specified arguments
/// asynchronously on a thread pool thread.
/// </summary>
public static void FireAndForget(Delegate d, params object[] args)
{
// Invoke the wrapper asynchronously, which will then
// execute the wrapped delegate synchronously (in the
// thread pool thread)
wrapperInstance.BeginInvoke(d, args, callback, null);
}
/// <summary>
/// Invokes the wrapped delegate synchronously
/// </summary>
private static void InvokeWrappedDelegate(Delegate d, object[] args)
{
d.DynamicInvoke(args);
}
/// <summary>
/// Calls EndInvoke on the wrapper and Close on the resulting WaitHandle
/// to prevent resource leaks.
/// </summary>
private static void EndWrapperInvoke(IAsyncResult ar)
{
wrapperInstance.EndInvoke(ar);
ar.AsyncWaitHandle.Close();
}
#region Nested type: DelegateWrapper
/// <summary>
/// Delegate to wrap another delegate and its arguments
/// </summary>
private delegate void DelegateWrapper(Delegate d, object[] args);
#endregion
}
}
The basic pattern to "fill in" a custom FireAndForget method for whatever
it is you may wish to do is:
1) Declare a Delegate that exactly maches the signature of your proposed method,
whose return type is void.
2) Create a private static void method that accepts your required parameters, and
does your actual business logic.
3) Create a public static void method to accept your required parameters, and inside
this method, call
FireAndForget(new YourDelegate(YourPrivateMethod), new object[] { param1,param2,...
});
It is important to understand that such calls are are placed in the Threadpool queue
until they can be serviced as threads become available, so if your Threadpool
is very busy, your method call may not be executed immediately.
I've provided a simple Windows Form test app to go with this, and as long as you
have Northwind installed, it has a button that will add the required test sproc
of "InsertCategory" for you. This uses the DAAB SqlHelper class, so
all you need to do is pass in the connection string, name of the sproc, and an
object array of the parameter values in the sproc in the proper order. This has
the added benefit that it caches the SqlParameter collection and re-uses it on
subsequent calls, making it even faster.
You can download the Visual Studio 2010 Solution here.