Any disadvantage of using ExecuteReaderAsync from C# AsyncCTP

39,469

I disagree with Ricka on this. Async DB commands are not only good, they are critical in achieving scale, throughput and latency. His objection about the ramp up time of the thread pool applies only to a web server that experiences low traffic volumes.

In a high traffic situation (which is the only one that matters), the thread pool won't have to wait for 'injecting' new threads. Doing the SQL Commands asynchronously is important not only from the point of view of web server requests/threads health, but also from the point of view of total request lifetime/latency: uncorrelated DB calls can be done in parallel, as opposed to sequentially. This alone results usually in dramatic improvements in the latency of the HTTP request as experienced by the user. In other words, your pages load faster.

A word of advice though: SQL Command is not truly asynchronous until you enable Asynchronous Processing=true on the connection string. While this is not set (and by default is not, Edit: starting with .NET Framework < 4.5. Asynchronous Processing is no longer required) your 'asyncronous' calls to BeginExecuteReader are nothing but a sham, the call will launch a thread and block that thread. When true async processing is enabled in the connection string then the call is truly async and the callback is based on IO completion.

A word of caution: an async SQL command is completing as soon as the first result returns to the client, and info messages count as result.

create procedure usp_DetailsTagsGetAllFromApprovedPropsWithCount
as
begin
print 'Hello';
select complex query;
end

You've lost all benefits of async. The print creates a result that is sent back to the client, which completes the async command and execution on the client resumes and continues with the 'reader.Read()'. Now that will block until the complex query start producing results. You ask 'who puts print in the procedure?' but the print may be disguised in something else, perhaps something as innocent looking as an INSERT that executes without first issuing a SET NOCOUNT ON.

Share:
39,469
tugberk
Author by

tugberk

Senior Software Engineer and Tech Lead, with a growth mindset belief and 10+ years of practical software engineering experience including technical leadership and distributed systems. I have a passion to create impactful software products, and I care about usability, reliability, observability and scalability of the software systems that I work on, as much as caring about day-to-day effectiveness, productivity and happiness of the team that I work with. I occasionally speak at international conferences (tugberkugurlu.com/speaking), and write technical posts on my blog (tugberkugurlu.com). I currently work at Facebook as a Software Engineer. I used to work at Deliveroo as a Staff Software Engineer in the Consumer division, working on distributed backend systems which have high throughput, low latency and high availability needs. Before that, I used to work at Redgate as a Technical Lead for 4 years, where I led and line-managed a team of 5 Software Engineers. I was responsible for all aspects of the products delivered by the team from technical architecture to product direction. I was also a Microsoft MVP for 7 years between 2012-2019 on Microsoft development technologies.

Updated on January 27, 2020

Comments

  • tugberk
    tugberk over 4 years

    There are some articles which indicate that async database calls are bad idea in .NET.

    On C# Async CTP, there is a System.Data.SqlClient.SqlCommand extension called ExecuteReaderAsync. I have some operations as below on my existing code:

    var connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["hubConnectionString"].ConnectionString;
    
    using (var conn = new SqlConnection(connectionString)) {
        using (var cmd = new SqlCommand()) {
    
            cmd.Connection = conn;
            cmd.CommandText = "sp$DetailsTagsGetAllFromApprovedPropsWithCount";
            cmd.CommandType = System.Data.CommandType.StoredProcedure;
    
            conn.Open();
    
            var reader = cmd.ExecuteReader();
            while (reader.Read()) {
    
                //do the reading
    
            }
    
            conn.Close();
        }
    }
    

    There are several operations like this on my code. So, I am having thoughts on converting those to async.

    But on the other hand, I am not seeing much attraction on this approach out there (maybe I am not looking at the right direction, who knows!).

    So, is there any disadvantages of using this new async programming model here?

    Edit:

    Assuming I refactor the code as below:

    public async Task<IEnumerable<Foo>> GetDataAsync() { 
    
        List<Foo> foos = new List<Foo>();
    
        var connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["hubConnectionString"].ConnectionString;
    
        using (var conn = new SqlConnection(connectionString)) {
            using (var cmd = new SqlCommand()) {
    
                cmd.Connection = conn;
                cmd.CommandText = "sp$DetailsTagsGetAllFromApprovedPropsWithCount";
                cmd.CommandType = System.Data.CommandType.StoredProcedure;
    
                conn.Open();
    
                var reader = await cmd.ExecuteReaderAsync();
                while (reader.Read()) {
    
                    //do the reading
                    //create foos
    
                }
    
                conn.Close();
            }
        }
    
        return foos;
    
    }
    

    As far as I understand from the await keyword, it converts the code, which is after it, as continuation. Also, when it hits the await keyword, it immediately returns to its caller regardless of the operation status. When it finishes it comes back and fire the continuation code.

    This is what I have in mind.

  • tugberk
    tugberk about 12 years
    "Command is not truly asynchronous until you enable Asynchronous Processing=true on the connection string." Is it still applicable if I use the new async programming model as I indicate on my question?
  • Chris Marisic
    Chris Marisic about 12 years
    Good lord who make design decisions like this? These gotchas are absurd.
  • Remus Rusanu
    Remus Rusanu about 12 years
    @tugberk: I'm pretty sure ExecuteReaderAsync is nothing but a wrapper around BeginExecuteReader/EndExecuteReader, therefore the connection string requirement applies to it as well.
  • tugberk
    tugberk about 12 years
    I don't think so (while I am not sure). New async programming model is a lot different I think. First of all, it is Task based.
  • Remus Rusanu
    Remus Rusanu about 12 years
    Yes, but the underlying async capabilities come from the SqlClient component. The Async team is building the language and .Net Framework capabilities leveraging the existing IAsyncResult features, not reinventing each one from scratch.
  • tugberk
    tugberk about 12 years
    yes, right. It's what it does: public static Task<SqlDataReader> ExecuteReaderAsync(this SqlCommand source) { return Task<SqlDataReader>.Factory.FromAsync(new Func<AsyncCallback, object, IAsyncResult>(source.BeginExecuteReader), new Func<IAsyncResult, SqlDataReader>(source.EndExecuteReader), null); }
  • Remus Rusanu
    Remus Rusanu about 12 years
    BTW as of .Net 4.5 there is new SqlDataReader.ReadAsync method.
  • chengbo
    chengbo over 10 years
    Beginning in the .NET Framework 4.5, these methods no longer require Asynchronous Processing=true in the connection string. msdn.microsoft.com/en-us/library/hh211418.aspx
  • Hackerman
    Hackerman almost 8 years
    Excellent, i'm beginning to use async methods for my database calls.