【问题标题】:Using awaited [async tasks] in different levels of architecture C#在不同层次的架构 C# 中使用等待的 [异步任务]
【发布时间】:2019-10-06 11:24:46
【问题描述】:

我一直在努力检查在哪里等待和在哪里不等待。

我有一个从数据库中获取数据的 Repository 类。 使用 EntityFramework 的代码是这样的:

public async Task<List<Object>> GetAsync()
{
  return await context.Set<Object>().ToListAsync();
}

和消费者:

var data = await GetAsync();

在顶层,我也在等待这种方法。 我应该只在其中一种方法上使用 await 吗? 每次等待时使用资源和创建新线程是否会降低性能?

我已经检查了 cmets 中列出的问题,它们并没有提到性能问题,只是说你可以做到。我想要最佳实践以及为什么这样做/不这样做的原因。

【问题讨论】:

  • 如果你唯一要做的就是调用另一个异步方法,那么通常你不应该在这种情况下使用 await。如果您必须将该调用放入 using 语句或 try/catch 块中,则继续使用 await,但在您的第一个示例中,您可能应该取出 async/await 并直接返回任务,即。 public Task&lt;List&lt;Object&gt;&gt; GetAsync() { return context.Set&lt;Object&gt;().ToListAsync(); }
  • 公平地说,“规则”不止于此。如果您的方法以调用返回任务的方法结束,并且这是整个方法中涉及的唯一任务,并且在此任务完成后您不需要在方法中执行任何操作(例如退出 try/catch、退出一个使用,没有更多的代码,没有这个),那么你不需要让你的方法async,你可以简单地返回任务并节省一些(轻微的)开销。

标签: c# asynchronous async-await


【解决方案1】:

让我首先了解您问题的本质,混淆与在完整的调用链中在哪里使用异步以及在哪里不使用以及如何评估使用的性能影响有关,因为它可能会导致创建更多线程。如果大纲超出此范围,请向 cmets 添加详细信息,我也会尝试回答。

让我们一一划分并解决它们。

在调用链中哪里使用异步,哪里不使用?

  • 在这里,当您使用实体框架访问数据库时,我可以安全地假设您正在使用基于 IO 的异步处理,这是跨语言和框架的异步处理最突出的用例,基于 CPU 的异步处理的用例是相对有限(也会解释)
  • 异步是一种可扩展性功能,尤其适用于 IO 处理而不是性能功能,简而言之,通过异步处理,您可以确保托管服务器可以满足更多的 IO 处理调用,因为调用不会阻塞并且它们只是手通过网络处理请求,同时进程线程返回池准备服务另一个请求,在几毫秒内完成进程移交
  • 处理完成后,软件线程只需接收它们并传回给客户端,同样是几毫秒,如果它是纯粹的传递,则大约

有什么好处

  • 想象一下,改为对数据库进行 IO 同步调用,其中涉及的每个线程只会等待结果到达,这可能会在几秒钟内完成,影响将是非常负面的,通常基于线程池大小,您可能会服务器最多 25 - 50 个请求,它们也会回复可处理的核心数量,在它们空闲并等待响应时会不断浪费资源
  • 如果您进行同步调用,则无法在同一设置中处理 1000 多个请求,我非常保守,异步实际上可以产生巨大的可扩展性影响,对于轻量级调用,它可以轻松地从单个托管进程处理数百万个请求

后台之后,完整链中的Async在哪里使用

  • 无处不在,从开始到结束,从入口点到实际出口点进行 IO 调用都是可行的,因为这是释放池线程的实际调用,因为它通过网络调度调用
  • 请记住await 在给定点不允许进一步的代码处理相同的方法,即使它减轻了线程,所以最好如果有多个独立调用,它们是使用Task.WhenAll聚合并等待代表任务,当它们全部完成成功/错误时返回,可能是什么状态
  • 如果使用 Task.WaitTask.Result 之类的东西在两者之间中断 Async,它将不会保持纯 Async 调用,并且会阻塞调用线程池线程

如何进一步增强异步?

  • 在纯库调用中,异步由线程池发起,调度线程可以与接收线程不同,调用不需要重新进入相​​同的上下文,你应该使用ConfigureAwait(false),这意味着它不会等待重新输入原始上下文,提高性能
  • await 一样,在整个链中使用ConfigureAwait(false) 是有意义的,从入口到最后。这仅适用于广泛回复线程池的库

是否创建了线程

变化

  • 基于 CPU 的异步处理,您在后台处理事情,因为当前线程需要响应,主要是在 WPF 等 Ui 的情况下

用例

  • 各种系统尤其是非MS框架,比如node js,都以Async处理为底层原理,接收端的数据库服务器集群经过调整,可以接收数百万次调用并进行处理
  • B2C 调用,期望每个请求都是轻量级的,Payload 有限

编辑 1:

仅在here 列出的这种特定情况下,ToListAsync 默认情况下是异步的,因此您可以跳过 variopus cmets 中列出的那种情况下的异步等待,但请总体上查看 Stepehen Cleay 的文章,这可能不是非常好的策略,因为收益微乎其微,错误使用的负面影响可能很大

【讨论】:

    【解决方案2】:

    我想补充一点。 有一些async 方法不需要使用async/await 关键字。检测这种滥用很重要,因为添加 async 修饰符是有代价的。

    例如您的示例中不需要 async/await 关键字。

    public Task<List<Object>> GetAsync()
    {
      return context.Set<Object>().ToListAsync();
    }
    

    然后:

    var data = await GetAsync();
    

    会好的。在这种情况下,您将返回 Task&lt;List&lt;Object&gt;&gt;,然后在您直接使用对象的地方等待它。

    我推荐安装async await helper

    【讨论】:

    • 即使在这种情况下,Async Await 也绝不是滥用,它只是 ToListAsync 即使没有等待也会自动异步,检查Eliding Async Await,通常建议不要省略,因为收益非常小,负面影响可能很大
    • 哦,对了。感谢您的链接,非常有用的信息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-06
    • 2013-03-08
    • 2014-03-18
    • 2014-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多