FireAndForget Asynchronous Utility Class for SQL Server Inserts and Updates

I have used the FireAndForget pattern for a number of years; the first implementation I ever saw was by Mike Woodring. FireAndForget provides a threadsafe, non-blocking framework to invoke methods without having to wait for the method to return. In other words, it does not matter what your method does or how long it takes, once you hand it off to the FireAndForget utility method, your call returns immediately.

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.

By Peter Bromberg   Popularity  (2607 Views)