Matthew
Matthew

Reputation: 4339

Is there a downside to using async?

Let's say I have an async method that uses Entity Framework to fetch stuff from the database. For example:

public async Task<MyEntity> Get(string bar){
    return await db.MyEntities.Where(x=>x.Foo==bar).SingleOrDefaultAsync();
}

This is mainly going to be used by an async MVC or Web API controller, and that works fine. But maybe I also occasionally want to use it in a console app, which doesn't have an async method. I can do that by using Task.Run

private static async Task MainAsync()
{
    using(MyEntityService repo=new MyEntityService())
    {
        MyEntity myEntity=await repo.Get("foobar");

        //do stuff
    }
}

public static void Main()
{
    Task.Run(async () =>
    {
        await MainAsync();
    }).Wait();
}

My question is: is there a downside to doing this compared to implementing a non-async version of the Get method? Assuming I'm comfortable with this level of code complexity, are there performance reasons a non-async method could be better?

Upvotes: 1

Views: 1637

Answers (2)

Lasse V. Karlsen
Lasse V. Karlsen

Reputation: 391326

Before I try to give something that resembles an answer to your question I feel like I need to make you aware of an important issue, and that is that with C# 7.1 or above, you can have an async Main method. You can read more about this in What's new in C# 7.1 but TL;DR, after switching your project to using C# 7.1 or higher, or "latest", you can do this:

public class Program
{
    public static async Task Main(string[] args)
    {
        using(MyEntityService repo=new MyEntityService())
        {
            MyEntity myEntity=await repo.Get("foobar");

            //do stuff
        }
    }
}

Now, let's try to answer your question. Bear in mind that my knowledge about async/await might be flawed/incomplete, so other answers that does a better job of it may surface.

With that out of the way, what is the most important thing async will add to your code? Complexity.

Any time you declare a method as async, the whole method compilation changes. The whole thing is turned into a state machine and be rewritten and moved. If you later decompile your program, unless the decompiler is of the really advanced type, the decompiled code will look nothing like your original program.

Now, will this state machine add noticeable overhead to program execution, in terms of execution time or memory usage? No, not really.

A different question would be how much overhead using the async version vs. the non-async version would add, and there is no way of answering that. The whole machinery and all the different options might mean that there is a lot, or there might be almost none, it depends on the actual API you're using and what/how it is doing.

One of the issues you will have to deal with is when it comes to exceptions. Stack traces becomes ... interesting ... when you involve methods declared as async.

For instance, given this short program (that I ran using LINQPad):

async Task Main()
{
    await Test1();
}

public static async Task Test1()
{
    await Task.Delay(1000);
    throw new Exception("Test");
}

You might expect the stack trace of the exception to contain Main, but alas, due to the fact that the throw statement is executed after the task has "resurfaced" after being delayed, it will be executed by some framework code and the stack trace actually looks like this:

   at UserQuery.<Test1>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at UserQuery.<Main>d__0.MoveNext()

I guess what you're really looking for are some guidelines about how to deal with async code, and the best I can do are these:

  • If you're writing synchronous code, and dealing with an API that has synchronous methods, use the synchronous methods
  • If you're writing asynchronous code, and dealing with an API that has asynchronous methods, use the asynchronous methods
  • If you're writing synchronous code, and dealing with an API that has asynchronous methods, use the asynchronous methods, use .Wait() or .Result to get the result and know exactly what you're doing
  • If you're writing asynchronous code, and dealing with an API that has synchronous methods, use the asynchronous methods

(Bear in mind that I did not in any way talk about what the methods of the API were doing; my assumption is that if an API provides async methods, there's a reason for it)

Upvotes: 2

Ilya Sulimanov
Ilya Sulimanov

Reputation: 7836

Well non-async method will better if you don't have any I/O-bound operations (such as reading from db/disk or network) in your action, because async/await has additional extra cost. Otherwise async action could be a good choice due to release a IIS's thread

Upvotes: 1

Related Questions