1

Let's say I have 3 methods as follows:

public async Task<int> CalculateData(int index, int trustLevel, string type)
{
    var x = await CalculateBaseScore(index, type);
    return ComplexProcess(x, trustLevel);
}

public async Task<int> CalculateBaseScore(int index, string type)
{
    var conn_string = "...";
    var sql = "...";
    var param = new { Index = index, Type = type };
    using (var conn = new SqlConnection(conn_string))
    {
        var db_output = await conn.ExecuteScalarAsync<int>(sql, param: param);
        return db_output;
    }
}

public int CalculateBaseScore(int index, int trustLevel)
{
    //Omitted
}

As you can see, 2 of them use async-await.

In Stephen Cleary's introduction to async-await, it's said that

If you can write it without await, then you should write it without await, and remove the async keyword from the method. A non-async method returning Task.FromResult is more efficient than an async method returning a value.

In this case, will a Dapper-made database call qualify as a method I can write without async-await and simply return a Task<int> object? Will I get any benefits in performance or other aspects if I change it to this?

public async Task<int> CalculateData(int index, int trustLevel, string type)
{
    var x = await CalculateBaseScore(index, type);
    return ComplexProcess(x, trustLevel);
}

public Task<int> CalculateBaseScore(int index, string type)
{
    var conn_string = "...";
    var sql = "...";
    var param = new { Index = index, Type = type };
    using (var conn = new SqlConnection(conn_string))
    {
        var db_output_task = conn.ExecuteScalarAsync<int>(sql, param: param);
        return db_output_task;
    }
}

public int CalculateBaseScore(int index, int trustLevel)
{
    //Omitted
}

See tht in the second example, I omitted the async-await keywords on CalculateBaseScore and simply returned the Task object.

Léster
  • 1,177
  • 1
  • 17
  • 39
  • Here's a [helpful link](https://cpratt.co/async-tips-tricks/). Look for the heading "Eliding the async/await keywords." The answer is that yes, you can omit `async` and just return the task as you are in your Dapper example. – Scott Hannen Apr 06 '20 at 19:39
  • 2
    The question "will I get performance wins if I do X" can only be answered definitively by doing X and doing not X, and comparing a user-focussed, carefully-measured performance metric. The only person who can do that is you; if you have two horses and you want to know which is the faster, asking strangers on the internet who have never seen your horses to guess is not a substitute for simply racing the horses! – Eric Lippert Apr 06 '20 at 19:43
  • His information is outdated. While Task DOES have an overhead, there is a reason there is now ValueTask which is a struct and faster. This part is thus - simply outdated. – TomTom Apr 06 '20 at 19:46
  • 2
    @TomTom: We did not create value tasks because they are *faster*. We created them because they lower collection pressure in the scenario where the task is likely already completed. – Eric Lippert Apr 06 '20 at 19:47
  • FWIW, you're not alone. in the past two years I've progressed from understanding async/await all wrong to mostly understanding it most of the time and googling the rest. – Scott Hannen Apr 06 '20 at 20:00
  • 3
    Also, as Theodor's answer points out, the question "which of these choices is faster?" requires that the two workflows being compared are *both correct* and *do the same thing*. Concentrate on that first! – Eric Lippert Apr 06 '20 at 20:20
  • 2
    I have changed my position in the intervening years, and I now [recommend against eliding `async`/`await`](https://blog.stephencleary.com/2016/12/eliding-async-await.html) except in the most trivial of cases. I shall update the older post right now. – Stephen Cleary Apr 09 '20 at 16:45

1 Answers1

3

Regarding the second implementation without async-await:

public Task<int> CalculateBaseScore(int index, string type)
{
    var conn_string = "...";
    var sql = "...";
    var param = new { Index = index, Type = type };
    using (var conn = new SqlConnection(conn_string))
    {
        var db_output_task = conn.ExecuteScalarAsync<int>(sql, param: param);
        return db_output_task;
    }
}

...will give you a performance boost of around 1 μsec per invocation. In other words it will save you a whole second of CPU time after invoking it 1,000,000 times. These are the good news.

The bad news are that all these 1,000,000 invocations will result to an ObjectDisposedException, because the SqlConnection will be disposed prematurely, immediately after the creation of the Task.

Does it worth it? I'd say no. Keeping it simple will save you more time in the long run.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 3
    Excellent point; always remember that a workflow *must* have an `await` at positions in the workflow where the remainder of the workflow must not proceed until the earlier portion has *completed*. If it is a requirement that the cleanup code at the end of the workflow *not execute* until the beginning of the workflow is *complete* then there needs to be an `await`. – Eric Lippert Apr 06 '20 at 20:19