C# 使用异步是否有缺点?

C# 使用异步是否有缺点?,c#,entity-framework,async-await,C#,Entity Framework,Async Await,假设我有一个异步方法,它使用实体框架从数据库中获取数据。例如: public async Task<MyEntity> Get(string bar){ return await db.MyEntities.Where(x=>x.Foo==bar).SingleOrDefaultAsync(); } 我的问题是:与实现非异步版本的Get方法相比,这样做是否有缺点?假设我对这一级别的代码复杂度感到满意,那么非异步方法的性能会更好吗?在我尝试给出类似于您问题答案的答案之前

假设我有一个异步方法,它使用实体框架从数据库中获取数据。例如:

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

我的问题是:与实现非异步版本的
Get
方法相比,这样做是否有缺点?假设我对这一级别的代码复杂度感到满意,那么非异步方法的性能会更好吗?

在我尝试给出类似于您问题答案的答案之前,我觉得我需要让您意识到一个重要问题,即对于C#7.1或更高版本,您可以使用异步Main方法。你可以阅读更多关于这一点,但TL;DR,将项目切换到使用C#7.1或更高版本或“最新版本”后,您可以执行以下操作:

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

            //do stuff
        }
    }
}

现在,让我们试着回答你的问题。请记住,我对async/await的了解可能有缺陷/不完整,因此可能会出现其他更好的答案

这样一来,async将为您的代码添加的最重要的内容是什么复杂性

任何时候将方法声明为
async
,整个方法编译都会更改。整个过程被转化为一个状态机,然后被重写和移动。如果您以后反编译您的程序,除非反编译器是真正高级的类型,否则反编译的代码将与原始程序完全不同

现在,在执行时间或内存使用方面,这个状态机会给程序执行增加明显的开销吗?不,不是真的

另一个问题是使用异步版本与非异步版本会增加多少开销,没有办法回答这个问题。整个机制和所有不同的选项可能意味着有很多,也可能几乎没有,这取决于您使用的实际API以及它正在做什么/如何做

您必须处理的问题之一是异常。堆栈跟踪变为。。。有趣的。。。当涉及声明为
async
的方法时

例如,给定这个简短的程序(我使用它运行):

您可能期望异常的堆栈跟踪包含
Main
,但遗憾的是,由于
throw
语句是在任务延迟后“重新显示”后执行的,因此它将由一些框架代码执行,堆栈跟踪实际上如下所示:

   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()
位于UserQuery.d_uu1.MoveNext()的

---来自引发异常的上一个位置的堆栈结束跟踪---
在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()中
在System.Runtime.CompilerServices.TaskWaiter.HandleNonSuccessAndDebuggerNotification(任务任务)中
在UserQuery.d_uu0.MoveNext()中

我想你真正想要的是一些关于如何处理异步代码的指导方针,我能做的最好的是:

  • 如果您正在编写同步代码,并且正在处理具有同步方法的API,请使用同步方法
  • 如果您正在编写异步代码,并且正在处理具有异步方法的API,请使用异步方法
  • 如果您正在编写同步代码,并且正在处理具有异步方法的API,请使用异步方法,使用
    .Wait()
    .Result
    来获得结果,并确切地知道您在做什么
  • 如果您正在编写异步代码,并且正在处理具有同步方法的API,请使用异步方法

(请记住,我没有以任何方式谈论API的方法在做什么;我的假设是,如果API提供异步方法,这是有原因的)

在我尝试给出类似于您问题答案的内容之前,我觉得我需要让您意识到一个重要问题,也就是说,在C#7.1或更高版本中,可以使用异步Main方法。你可以阅读更多关于这一点,但TL;DR,将项目切换到使用C#7.1或更高版本或“最新版本”后,您可以执行以下操作:

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

            //do stuff
        }
    }
}

现在,让我们试着回答你的问题。请记住,我对async/await的了解可能有缺陷/不完整,因此可能会出现其他更好的答案

这样一来,async将为您的代码添加的最重要的内容是什么复杂性

任何时候将方法声明为
async
,整个方法编译都会更改。整个过程被转化为一个状态机,然后被重写和移动。如果您以后反编译您的程序,除非反编译器是真正高级的类型,否则反编译的代码将与原始程序完全不同

现在,在执行时间或内存使用方面,这个状态机会给程序执行增加明显的开销吗?不,不是真的

另一个问题是使用异步版本与非异步版本会增加多少开销,没有办法回答这个问题。整个机制和所有不同的选项可能意味着有很多,也可能几乎没有,这取决于您使用的实际API以及它正在做什么/如何做

您必须处理的问题之一是异常。堆栈跟踪变为。。。有趣的。。。当涉及声明为
async
的方法时

例如,给定这个简短的程序(我使用它运行):

您可能期望异常的堆栈跟踪包含
Main
,但遗憾的是,由于
throw
语句是在任务延迟后“重新显示”后执行的,因此它将由一些框架代码执行,堆栈跟踪实际上如下所示:

   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()
位于UserQuery.d_uu1.MoveNext()的

---来自引发异常的上一个位置的堆栈结束跟踪---
在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()中
在System.Runtime.CompilerServices.TaskAwaiter.HandleNonSucces