Entity Framework asynch – behind the magic

As many devs have probably found out (http://blogs.msdn.com/b/adonet/archive/2012/10/30/ef6-alpha-1-available-on-nuget.aspx), the next Entity Framework version (6.0) will support the task-based asynchronous patterns that were introduced in .NET 4.5 (async and all the stuff).

I won’t go into details on why asynch is useful, what problems does it solve (very short and oversimplified: UI responsiveness in client applications and limited number of threads from thread pool that wait after a blocking, non CPU-bound operations, in web applications under heavy load).
For a very good (but deep) tutorial on asynch in C# 5.0, anybody can read the series of post in Erric Lipper blog: http://blogs.msdn.com/b/ericlippert/archive/tags/async/.
There I learned how the new async keyword is just a syntactic sugar, but a very powerful one – implementing the equivalent code with the existing language features is possible, but can become very painful, quickly, when the case is non-trivial.

I also won’t go into details why asynchronous DB calls aren’t always the best idea (http://blogs.msdn.com/b/rickandy/archive/2009/11/14/should-my-database-calls-be-asynchronous.aspx), even on a busy web application (on desktop apps directly accessing the DB, a background thread can do the job just fine).

On a practical note, why async support was added to EF (not an easy task)? Probably the main reason was future support for EF inside WinRT, where each call longer than 50ms must be asynchronous (sure, most WinRT applications won’t need a local database, but still).

When I first tried the async support in EF 6 alpha 1, I was curious to know what is really happening when an async db call is done using EF, and this is not explained in many blog posts – a new thread is spawned or..? Fortunately, now most of the stack, EF6 and many parts of .NET Framework are open source, so I can see what’s happening.
For EF6 we can even do a
git clone https://git01.codeplex.com/entityframework.git
and have a local copy of the entire source code.
For .NET Framework source code, the easiest way is to use Resharper to get with a simple F12 the original source code from http://referencesource.microsoft.com/netframework.aspx in a transparent way.

So, what’s really happening behind a nice call like this?

        private static async Task SaveInput(string question, string answer)
        {
            using (var ctx = new AnswersContext())
            {
                var answerObj = new Answer() { Content = answer };
                // ...
                await ctx.SaveChangesAsync();
            }
        }

After I skipped over several layers of Entity Framework code, at the bottom of EF implementation, I found this, somewhere deep inside DynamicUpdateCommand class:

 
internal override async Task<long> ExecuteAsync(
//...
rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
//...

where ‘command’ is a DbCommand
so, yes, EF6 is just delegating the async call to ADO.NET provider, no new thread created yet.
No Resharper needed yet, since I had the source code of EF6 alpha.

Does ADO.NET have task-based async calls? Yes it does, since .NET 4.5.
What does this means? That all companies that offer ADO.NET providers will have to update them in order to offer true task-based async calls.
Fortunately, the base class (DbCommand), if the method is not overridden, executes the synchronous version:

public virtual Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken)
// ...
return Task.FromResult<int>(this.ExecuteNonQuery());
// ... 

Since we are using the ADO.NET provider for SQL Server, hopefully, this method is truly asynch, so if we dig inside SqlCommand from .NET Framework 4.5 (with a decompiler), we find this:

public override Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken)
{ // ...
Task<int>.Factory.FromAsync(
  new Func<AsyncCallback, object, IAsyncResult>(this.BeginExecuteNonQueryAsync), 
  new Func<IAsyncResult, int>(this.EndExecuteNonQueryAsync), (object) null)
         .ContinueWith((Action<Task<int>>) (t => // ...
// ...
}

going again to the bottom of SqlClient asynchronous implementation, we encounter the same code which is executed when BeginExecuteNonQuery is called, which is present in ADO.NET since a long time (.NET 2.0).
Even if the code was refactored since those times, the description on how this is achieved remains valid:
Asynchronous Command Execution in ADO.NET 2.0
ADO.NET/SqlClient asynchronous command execution support is based on true asynchronous network I/O under the covers (or non-blocking signaling in the case of shared memory)

More details on what happens at TDS level (the protocol used to communicate with SQL Server) is described at:
http://blogs.msdn.com/b/adonet/archive/2012/04/20/using-sqldatareader-s-new-async-methods-in-net-4-5-beta.aspx

Below that is only the TDS implementation and the support offered by Windows for shared memory or async network I/O, a very interesting subject in itself, for those who like to understand the building blocks.
For a more high-level explanation on why async in C# 5.0 does not require any extra thread (except those already created by the operating system and shared by many applications): http://blogs.msdn.com/b/ericlippert/archive/2010/11/04/asynchrony-in-c-5-0-part-four-it-s-not-magic.aspx

The conclusion: trust the implementation :), but read an article like http://msdn.microsoft.com/en-us/magazine/hh456402.aspx on async performance before going in that direction.
Also, if somebody is looking for a fire-and-forget asynch call into SQL Server, when the result does not matter, asynch calls from ADO.NET are not the answer – Remus Rusanu has an interesting article: http://rusanu.com/2009/08/05/asynchronous-procedure-execution/

Advertisements
This entry was posted in .NET, Entity Framework, Uncategorized and tagged , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s