【问题标题】:Is there a downside to using async?使用异步有缺点吗?
【发布时间】:2018-10-13 21:29:05
【问题描述】:

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

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 方法的非异步版本相比,这样做是否有缺点?假设我对这种级别的代码复杂性感到满意,是否存在非异步方法可能更好的性能原因?

【问题讨论】:

  • 知道使用 C# 7.1 及更高版本您可以拥有async Main
  • 你为什么在Main中使用Task.Run
  • Task.Run() 没有功能。只需致电MainAsync().Wait();。通常不行,但这是 Main() 所以你被允许了。
  • async Task Main() 只是装点门面,它只会将.Wait() 移动到你看不到的地方。

标签: c# entity-framework async-await


【解决方案1】:

在我尝试给出类似于您的问题的答案之前,我觉得我需要让您了解一个重要问题,那就是使用 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 提供异步方法,则有理由它)

【讨论】:

    【解决方案2】:

    如果您的操作中没有任何 I/O 绑定操作(例如从数据库/磁盘或网络读取),那么非异步方法会更好,因为异步/等待有额外的额外成本。否则,由于释放 IIS 的线程,异步操作可能是一个不错的选择

    【讨论】:

    猜你喜欢
    • 2019-06-08
    • 2023-03-03
    • 2011-11-05
    • 1970-01-01
    • 1970-01-01
    • 2019-12-30
    • 1970-01-01
    • 1970-01-01
    • 2011-09-24
    相关资源
    最近更新 更多