使用异步有不利之处吗?

时间:2018-10-13 21:29:05

标签: c# entity-framework async-await

假设我有一个异步方法,该方法使用Entity Framework从数据库中获取内容。例如:

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

这主要将由异步MVC或Web API控制器使用,并且工作正常。但是也许我偶尔也想在没有异步方法的控制台应用程序中使用它。我可以使用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();
}

我的问题是:与实现Get方法的非异步版本相比,这样做有不利之处吗?假设我对这种级别的代码复杂性感到满意,是否有性能原因可以使非异步方法更好?

2 个答案:

答案 0 :(得分:2)

在尝试给出类似于您的问题的答案之前,我觉得我需要让您意识到一个重要的问题,那就是对于C#7.1或更高版本,您可以使用异步Main方法。您可以在What's new in C# 7.1中阅读有关此内容的更多信息,但请阅读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时,整个方法编译都会更改。整个过程变成一个状态机,并被重写和移动。如果以后再对程序进行反编译,除非反编译器是真正的高级类型,否则反编译后的代码将看起来像原始程序一样。

现在,此状态机是否会在执行时间或内存使用方面增加程序执行的开销?不,不是真的。

一个不同的问题是,使用异步版本和使用非异步版本会增加多少开销,而没有办法解决。整个机制以及所有不同的选项可能意味着有很多,或者可能几乎没有,这取决于您使用的实际API以及它的作用/方式。

关于异常,您将要处理的问题之一。当涉及到声明为async的方法时,堆栈跟踪就变得很有趣。

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

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

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

您可能希望异常的堆栈跟踪包含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()

我想您真正在寻找的是一些有关如何处理异步代码的准则,而我能做到的最好的是这些准则:

  • 如果您正在编写同步代码,并且要处理具有同步方法的API,请使用同步方法
  • 如果您正在编写异步代码,并且要处理具有异步方法的API,请使用异步方法
  • 如果您正在编写同步代码,并且要处理具有异步方法的API,请使用异步方法,并使用.Wait().Result获得结果并了解完全您在做什么
  • 如果您正在编写异步代码,并且要处理具有同步方法的API,请使用异步方法

(请记住,我并没有以任何方式谈论API的方法在做什么;我的假设是,如果API提供了异步方法,则有理由它)

答案 1 :(得分:1)

如果您没有任何I / O绑定操作(例如从数据库/磁盘或网络读取),那么非异步方法会更好,因为异步/等待会产生额外的额外费用。否则,由于释放IIS的线程

,异步操作可能是一个不错的选择