【问题标题】:Async/Await in multi-layer C# applications多层 C# 应用程序中的 Async/Await
【发布时间】:2013-05-16 14:58:02
【问题描述】:

我在高流量场景中有一个多层 C# MVC4 Web 应用程序,它为各种存储库使用依赖注入。这非常有用,因为它易于测试,并且在生产中我们可以轻松地为特定控制器配置存储库。所有控制器都继承自 AsyncController,因此返回 Task<JsonResult>Task<ActionResult> 的操作方法真正帮助我们的服务器扩展更多用户和 solve for the dreaded thread starvation problem。一些存储库使用 Web 服务,我们绝对可以使用 Async/Await 来提供可扩展性优势。在其他情况下,有些人可能使用无法安全线程化的非托管服务,其中 async/await 不会提供任何好处。每个存储库都是一个接口 IRepository 的实现。简而言之,每种实现在获取数据的方式上都大不相同。此配置是在部署时通过 web.config 更改和 Autofac 模块选择的。

在这样的应用程序中实现 async/await 的推荐方法有哪些?为了使该模式适合我现有的应用程序,我必须更改接口以返回Task<MyBusinessObject>但这不是更多的实现细节吗?我可以提供两个方法存根,GetDataGetDataAsync,但对我来说,这不允许我现在使用 IoC 框架,如Autofac,我可以轻松地交换所有内容,而无需更改代码。

Async/Await 的一位 Microsoft 开发人员发表了一篇博文“Should I expose an asynchronous wrapper for my synchronous methods?”,这就是问题所在。如果您的异步方法不是真正的异步(提供本机 I/O 优势),那么它实际上只会增加开销。换句话说,它不会提供任何可扩展性优势。

我只是想知道社区的其他成员是如何解决这个问题的。

【问题讨论】:

  • asyncawait 只是将阻塞方法转换为非阻塞方法的抽象。您可以在代码中的任何位置执行此操作,而无需更改接口,除非您想强制接口方法始终是异步的。
  • 如果在整个调用堆栈中都没有 Task,那么它永远不会被异步调用。因此,您必须将其放在界面中以始终返回 Task.
  • 我不是这么说的。我说的是您可以使用asyncawait 将任何方法转换为异步方法,包括您在接口上已有的任何阻塞方法。原始方法不需要 Task<TResult> 返回类型。当然,将所有接口方法替换为 async 等效项是完全有效的,但这不是必需的。
  • 这没有任何好处,它实际上增加了开销。查看 Stephen Toub 的博客文章,“我应该为我的同步方法公开一个异步包装器吗?” blogs.msdn.com/b/pfxteam/archive/2012/03/24/10287244.aspx
  • @CyanLite:当您不在整个调用堆栈中使用 Task 时,您的代码可以异步工作,但在这种情况下,您将无法从 IO 完成端口线程中受益,这在您的情况下似乎至关重要.

标签: c# interface repository-pattern async-await


【解决方案1】:

在您的情况下,我建议您将界面设为异步:

public interface IMyInterface
{
  Task<TMyResult> GetDataAsync();
}

对于同步方法,可以返回Task.FromResult

public class MyImplementation : IMyInterface
{
  public Task<TMyResult> GetDataAsync()
  {
    TMyResult ret = ...;
    return Task.FromResult(ret);
  }
}

异步方法当然可以是async,使用await

关于实施细节,我会说“是和不是”。 ;) 在这方面它与IDisposable 非常相似。从逻辑上讲,它是一个实现细节,因为它的实现方式无关紧要。在调用代码需要以不同方式处理它的意义上,它不是一个实现细节。所以我同意 - 从逻辑上讲 - 这是一个实现细节,但 .NET 平台还不够先进,无法将其视为这样。

另外,你可以这样想(这是我通常给出的解释):async 是实际的实现细节,当你返回 Task&lt;T&gt; 而不是 T 时,你定义了一个接口其中实现可能是异步的。

我有一个series of blog posts,该地址使用async“大”进行设计(即,将async 与OOP 配合使用,而不是让它发挥作用)。我解决了一些常见问题,例如异步初始化和 IoC。这只是一个人的意见,但我不知道有任何其他资源可以解决这类问题。

附:在 MVC4 中,您不再需要从 AsyncController 继承。默认控制器处理程序将根据返回类型(TaskTask&lt;T&gt;)自动检测 async 方法。

【讨论】:

  • 您的一系列博客文章的链接似乎已损坏。你能更新吗?
  • 没关系,Chrome 很愚蠢,没有正确“翻译”链接。感谢发帖。
  • 其实链接坏了;我今年早些时候更换了供应商。现在已经修好了,谢谢!
  • 啊,很高兴我提到了一些东西。好东西,顺便说一句。感谢您发布博客文章。但是,在调用异步方法和使用注入对象时,我找不到太多关于使用 DI/IoC 的信息(不幸的是,注释长度限制不会让我更具体,但是关于 DI/Ioc 和异步的任何信息你知道的将不胜感激)。
  • 我在this blog post 中有所介绍,但在MSDN article 中有更详细的介绍。如果您有具体的问题或疑问,您当然可以在这里问,除了我之外还有很多聪明人:)
【解决方案2】:

Task 是一个有漏洞的抽象。如果您希望受益于 IO 完成端口线程的使用,则必须将任务从您的存储库一路返回到您的控制器。

虽然让整个调用堆栈返回 Task&lt;T&gt; 可能会提高可伸缩性,但它确实为您的应用程序增加了一层额外的复杂性。这将阻碍可维护性、可测试性,并使推理代码变得更加困难。 Asynchronous code will always be harder than synchronous code.

就个人而言,我宁愿增加服务器的内存量并将应用程序配置为具有更大的线程池以补偿可伸缩性的损失,或者横向扩展到一些额外的(虚拟)服务器,而不是增加这种额外的复杂性.

【讨论】:

  • 我不同意额外的复杂性。 async/await 的可维护性、可测试性和代码推理很简单(一旦你理解了它们)。
猜你喜欢
  • 1970-01-01
  • 2013-01-08
  • 2015-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-24
  • 2018-09-24
  • 2013-01-12
相关资源
最近更新 更多