SqlCommand.BeginExecuteXxx, SynchronizationContext, and Anonymous Methods

Components that support the event-based asynchronous pattern significantly facilitate the use of asynchronous operations by making note of the synchronization context that is active when an asynchronous operation is started, and then using that context to make sure that the component's call to your operation-has-completed event handler later on happens in that same context.  Component's pull this off by internally using the AsyncOperationManager class that made it's debut in version 2.0 of the .NET Framework.  This alleviates the component consumer from having to worry about doing things like (in the case of Windows Forms programming) calling Control.InvokeRequired and Control.BeginInvoke.  In the context of an ASP.NET application, this same technique ensures that things like HttpContext.Current, Thread.CurrentCulture, and Thread.CurrentUICulture are all in place when your operation-has-completed event handler is invoked by the component.  The result is a unified model for dealing with asynchronous operations in library components that might be used in a variety of diverse application contexts.  Components like BackgroundWorker, WebClient, and WSDL.EXE-generated web service proxies (in 2.0) all support this mechanism for asynchronous operations.

Unfortunately, not all library classes that support asynchronous operations follow this new pattern for asynchronous execution.  One such example is the SqlCommand class.  In version 2.0 of the .NET Framework, SqlCommand sprouted 3 new methods that support the asynchronous execution of (typically long running) queries and stored procedures: BeginExecuteReader, BeginExecuteNonQuery, and BeginExecuteXmlReader.  As a result, if/when you use these methods, you need to take whatever steps are appropriate given the application environment you're executing in to deal with the fact that your command-completed callback method is going to be called in the wrong context.

Using a Windows Forms application as an example, this means that if you kick off an async operation like so...

void someButton_Click(object sender, EventArgs ea)
{
    SqlConnection conn = new SqlConnection(someConnectionString);
    conn.Open();
    SqlCommand cmd = new SqlCommand(someQuery, conn);
    SqlOperation sqlOp = new SqlOperation(conn, cmd);  // SqlOperation is defined at the end of this post.
    cmd.BeginExecuteReader(OnReadComplete, sqlOp);
}

...then your OnReadComplete handler had better be written to use InvokeRequired and BeginInvoke to marshal the callback over to the correct thread before you touch any of your controls:

void OnReadComplete(IAsyncResult ar)
{
    if( InvokeRequired )
    {
        BeginInvoke(new AsyncCallback(OnReadComplete), new object[] { ar });
        return;
    }

    SqlOperation sqlOp = (SqlOperation)ar.AsyncState;

    using (SqlDataReader rdr = sqlOp.Command.EndExecuteReader(ar))
    {
        someBindingSource.DataSource = rdr;
    }

    sqlOp.Complete();
}

That's no so bad - just tedious to do throughout your program.  But if you're using something like BeginExecuteReader in the context of an asynchronous ASP.NET page, then you need to resort to one of the techniques for starting and completed asynchronous tasks, which is more cumbersome than the Windows Forms InvokeRequired/BeginInvoke approach.

To facilitate using SqlCommand.BeginExecuteXxx in my own code, I've resorted to using a little helper class that takes care of using the AsyncOperationManager class to “do the right thing“ with respect to execution context.  This helper class (which I call SqlAsyncOperation) acts as a trampoline between the callback coming from the SQL provider and my own code, using the AsyncOperationManager to switch between synchronization contexts before making the callback to my own code.  In other words, instead of calling something like SqlCommand.BeginExecuteReader directly, I call the helper, which uses the AsyncOperationManager class to note the current synchronization context before forwarding my call to BeginExecuteReader.  As part of this, the helper substitutes its own operation-complete callback method for my own.  When that method is called later, the helper can make the switch to the correct synchronization context that was noted when the operation was kicked off and then call me back.  As a result, all of the context shuffling goo is hidden from my main application code.

I'll show two versions of the helper class.  The first, which is the one I use, takes advantage of anonymous methods to provide a nice, compact solution:

public class SqlAsyncOperation
{
    public delegate IAsyncResult BeginOperationDelegate(AsyncCallback completedHandler, object state);

    public static IAsyncResult Start(BeginOperationDelegate startProc, AsyncCallback completedHandler, object state)
    {
        AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
        return startProc(delegate(IAsyncResult ar) { asyncOp.PostOperationCompleted(delegate { completedHandler(ar); }, null); }, state);
    }
}

With the above helper class defined, I can change how I kick off the asynchronous query from this:

cmd.BeginExecuteReader(OnReadComplete, sqlOp);

to this:

SqlAsyncOperation.Start(cmd.BeginExecuteReader, OnReadComplete, sqlOp);

Note that instead of calling cmd.BeginExecuteReader directly, I now pass a delegate to that method to SqlAsyncOperation.Start.  Since BeginExecuteReader, BeginExecuteNonQuery, and BeginExecuteXmlReader all support the same method signature via one of their overloads, you can use this approach with any of those methods.   That's a fairly decent bang for my buck.  It's not much extra typing, but it means that I can count on my OnReadComplete method being called in the “correct“ context automagically without having to worry to much about how it was pulled off.  If you're writing a Windows Forms application, you can do this whenever you like.  If you're building an asynchronous page in ASP.NET, however, then this call is only valid in the context of Page_Load (due to restrictions ASP.NET places on when async operations can be kicked off).  But because SqlAsyncOperation.Start uses the standard AsyncOperationManager class to interact with the current SynchronizationContext, this technique works equally as well in ASP.NET asynchronous pages or Windows Forms applications.

Internally, SqlAsyncOperation.Start uses AsyncOperationManager.CreateOperation to “start“ an async operation (really just making a note of which SynchronizationContext is current), then call cmd.BeginExecuteReader for me.  As part of this, SqlAsyncOperation.Start passes a reference to an anonymous method to be invoked when the query completes.  This anonymous method takes care of calling AsyncOperation.PostOperationCompleted, which switches synchronization contexts as necessary, calls the specified method (another anonymous method that calls the original caller's operation-completed handler), and then completes the async operation.  The net result is that the application level code (whether Windows Forms or ASP.NET applications) can rest assured that the callback they receive when the query/sproc completes will take place in the same synchronization context that was active when the operation was originally started.

For a bit more clarity, here's the same implementation of the SqlAsyncOperation class shown above; annotated to indicate what context each anonymous method is called in (and with some gratuitous whitespace thrown in):

public class SqlAsyncOperation
{
    public delegate IAsyncResult BeginOperationDelegate(AsyncCallback completedHandler, object state);

    public static IAsyncResult Start(BeginOperationDelegate startProc, AsyncCallback completedHandler, object state)
    {
        AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);

        // Context: original/correct context.
        //
        return
            startProc(
                delegate(IAsyncResult ar)
                {
                    // Context: unknown/incorrect.
                    //
                    asyncOp.PostOperationCompleted(
                        delegate
                        {
                            // Context: original/correct.
                            //
                            completedHandler(ar);
                        },

                        null
                    );
                },

                state
            );
    }
}

And if the nested anonymous methods approach defined above is still too obfuscated for you even with the annotations and extra whitespace, here's a variation of the SqlAsyncOperation class that doesn't use anonymous methods at all:

public class SqlAsyncOperation
{
    public delegate IAsyncResult BeginOperationDelegate(AsyncCallback completedHandler, object state);

    public static IAsyncResult Start(BeginOperationDelegate startProc, AsyncCallback completedHandler, object state)
    {
        // Context: original caller (the “correct“ one).
        //
        SqlAsyncOperation helper = new SqlAsyncOperation(completedHandler, state);
        return startProc(helper.OnOperationCompleted, state);
    }

    AsyncOperation _asyncOp;
    AsyncCallback _completedHandler;

    SqlAsyncOperation(AsyncCallback completedHandler, object state)
    {
        _completedHandler = completedHandler;
        _asyncOp = AsyncOperationManager.CreateOperation(this);
    }

    void OnOperationCompleted(IAsyncResult ar)
    {
        // Context: arbitrary/the wrong one.  Request a
        // callback to NotifyComplete in the “correct“ context.
        //
        _asyncOp.PostOperationCompleted(NotifyComplete, ar);
    }

    void NotifyComplete(object state)
    {
        // Context: original caller (the “correct“ one).
        //
        _completedHandler((IAsyncResult)state);
    }
}

At any rate, I've found using this kind of SqlAsyncOperation helper class useful.  And being the twisted guy that I am, I loved how anonymous methods and its support for local variable capture saved me from all the extra typing.

P.S.

Here's the SqlOperation helper class that was used in the above code fragments.

class SqlOperation
{
    public readonly SqlConnection Connection;
    public readonly SqlCommand Command;

    public SqlOperation(SqlConnection conn, SqlCommand cmd)
    {
        Connection = conn;
        Command = cmd;
    }

    public void Complete()
    {
        Command.Dispose();
        Connection.Dispose();
    }
}


Posted Nov 29 2005, 01:56 PM by mike-woodring

Comments

Christopher Steen wrote Link Listing - November 29, 2005
on 11-29-2005 9:17 PM
.NET 1.1 vs. 2.0 - Do Not Rely in inbuilt
indexing [Via: sahilmalik ]
Automating Web Site Builds...
Mike Taulty's Weblog wrote AsyncOperationManager.
on 11-30-2005 7:34 AM
LA.Net wrote Wrapper para opera
on 12-01-2005 5:45 AM
Adam Toth wrote re: SqlCommand.BeginExecuteXxx, SynchronizationContext, and Anonymous Methods
on 12-01-2005 12:22 PM
Hey,

Great posting.

I'd like to use an EventBased asynchronous method from the WebClient class on an asp.net page, and was wondering if I needed to set Async="true" in the page directive and if I needed to use PageAsyncTasks as well?

I want to upload several files at once, and after they are finished redirect the user to a page. If I just call the UploadFileAsync method, will the request thread for the page be freed up for other requests like it is for PageAsyncTasks? Or will I not be able to reap any of the benefits of async pages in asp.net unless the method I'm caling implements the BeginXXX/EndXXX pattern?


Mike wrote re: SqlCommand.BeginExecuteXxx, SynchronizationContext, and Anonymous Methods
on 12-01-2005 12:53 PM
Yes, you'll need to set Async="true" on the page. Having done that, you can call WebClient.UploadFileAsync *from within Page_Load* as needed (if you try to call it later than Page_Load you'll get an exception). Because components like WebClient use the AsyncOperationManager class to 'start' the async operations, ASP.NET will know that you've started one or more async operations, and so will not complete the HTTP request until all of your operations complete. So you don't need to do anything with PageAsyncTasks (because you're using a component that supports the event-based async model already).

And yes - doing it this way will allow the thread that called Page_Load to be used by ASP.NET to service other requests in the meantime.
Adam Toth wrote re: SqlCommand.BeginExecuteXxx, SynchronizationContext, and Anonymous Methods
on 12-01-2005 2:46 PM
Great answer, thanks a bunch. I found your original post from a search, but you're on my RSS reader now.
shayke wrote re: SqlCommand.BeginExecuteXxx, SynchronizationContext, and Anonymous Methods
on 12-21-2005 3:12 AM
Hi
Great posting except you forgot to mention that this technique does not work with windowless threads.
e.g async operation can not report operation completed to a worker thread only to forms main thread.

shayke
Mike wrote re: SqlCommand.BeginExecuteXxx, SynchronizationContext, and Anonymous Methods
on 01-18-2006 8:06 AM
That's not true (or else I'm completely misunderstanding your assertion).

The AsyncOperationManager, and underlying SynchronizationContext, stuff is not specifically tied to forms or threads with forms. AOM.CreateOperation uses the static SynchronizationContext.Current property to get a reference to the current synch context. If it's null, then it sets the current SynchronizationContext to a new instance of the SynchronizationContext class, which is a noop implementation.

In a winforms app, the reference that's returned to AOM.CreateOperation will be to a winforms-specific derivative of SynchronizationContext. In an ASP.NET application, it will be to an ASP.NET-specific SynchronizationContext-derived class. In a console app, it will be the default noop implementation.

That's basically the point of the AsyncOperationManager/AsyncOperation/SynchronizationContext architecture - it just does the "right" thing, where what's "right" is anything from nothing to some application-specific context switching.
Jay wrote re: SqlCommand.BeginExecuteXxx, SynchronizationContext, and Anonymous Methods
on 06-08-2007 12:17 PM
Thanks for a clear explanation of what is going on and the ready to use class.

I had read that if I use an asynchronous call to a web service, or method in a web page that you need to be careful to not use a delegate as this will still tie up the thread pool.

Since you are using delegates to do the switching would this be one of those cases.

The main reason I am converting my calls to the database into asynch version is for greater scalability per server due to the worker thread limit.

Can you comment on this?
Mike wrote re: SqlCommand.BeginExecuteXxx, SynchronizationContext, and Anonymous Methods
on 06-12-2007 8:19 AM
The issue you're referring to with delegates in the past was using the Delegate.BeginInvoke feature to achieve asynchronous execution of a call against a web service, database server, etc. But in that scenario, you're really just using a pooled thread to perform a synchronous call against the target web service, which is what ties up the thread pool threads. So while you're thread isn't blocked - giving the appearance of asynchronous execution as far as your code is concerned - you've really just borrowed another thread from the pool & tasked it with blocking on your behalf, thereby tying it up & rendering it out of commission for the duration of the web service call/database operation/etc.

In this situation, delegates and the thread pool aren't being used to achieve asynchronous execution. The async execution is being provided by BeginExecuteReader, which performs asynchronous I/O at the network level. In other words, issue a read or write, then return immediately without blocking - and getting some sort of notification from the network layer subsequently that indicates the operation has completed. In this scenario, delegates are just used to provide the 'glue' between the code performing that asynch I/O, and the code you've got that's subsequently receiving a callback to notify you that said I/O operation has completed. But we're not involving/tying up a thread pool thread.

So there's no worries here.
Ulf wrote re: SqlCommand.BeginExecuteXxx, SynchronizationContext, and Anonymous Methods
on 01-22-2008 11:14 PM
I've read through your article an tried to replace my InvokeRequired mechanism. What i do is to query a database on program start. This query returns a big list of entries, that's why i query it async. After completion i create a DataTable and bind it to a control.

Using InvokeRequired everything works like expected. The database operation won't block the ui thread, and the data is bound to the control after the query completes.

If i use your approach, everything executes fine, but it seems that the operation already blocks the ui thread. Do you know why this happens?

Where do you tell the AsyncOperation, which operation it should execute async?

Add a Comment

(required)  
(optional)
(required)  
Remember Me?