FireAndForget Asynchronous Utility Class for SQL Server Inserts and Updates

By Peter Bromberg

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.

Popularity  (1328 Views)
Picture
Biography - Peter Bromberg
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites. Follow Microsoft MVP
Create New Account
Article Discussion: FireAndForget Asynchronous Utility Class for SQL Server Inserts and Updates
Peter Bromberg posted at Wednesday, October 13, 2010 3:53 PM
Scott Gall replied to Peter Bromberg at Tuesday, October 19, 2010 4:38 AM
Very interesting Article,

I have one question, if an error is raised from the public static void method could it be caught and handled in the method which called FireAndForget? or is this going to be out of scope at this point?

A Fire and Foreget approach can work exceptionally well if you are sure there will not be an unexpected error (though that's hard to guarentee) and I am curious how you would suggest dealing with such an occurance.

Thank you in advance for your time.
Peter Bromberg replied to Scott Gall at Tuesday, October 19, 2010 4:38 AM
It would be out of scope. You could wrap the logic in EndWrapperInvoke in a try / catch block and log the exception.
Scott Gall replied to Peter Bromberg at Tuesday, October 19, 2010 4:38 AM
Thanks for the quick responce, and a good idea.
Alex Joyce replied to Peter Bromberg at Tuesday, October 19, 2010 4:38 AM
Hi Peter

I Really like this great post, I am hoping you can answer one question for me, The SQL helper object is caching the parameters, but because it is being called from a new thread each time, is it not going to go out of scope and get picked up by the GC after each thread completes, hence when the next new thread executes it will always have to go to the db for the parameters as it will always have a new empty cache? I ask the question because I am thinking about using the SQL helper class in an async logging scenario I am working on and I wonder if I should use the explicit sql parameter array on ExecuetnNonQuery instead of the object array to save an extra round trip for picking up the parameters each time

Thanks in advance

Peter Bromberg replied to Alex Joyce at Tuesday, October 19, 2010 4:38 AM
The SqlHelper class caches only the parameters, not the parameter values. So each time it is called with the same stored proc name and connection string, it supplies your new values. This is threadsafe behavior.