多层C#应用程序中的异步/等待

多层C#应用程序中的异步/等待,c#,interface,repository-pattern,async-await,C#,Interface,Repository Pattern,Async Await,在高流量场景中,我有一个多层C#MVC4 web应用程序,它对各种存储库使用依赖注入。这非常有用,因为它易于测试,并且在生产中,我们可以轻松地为特定控制器配置存储库。所有控制器都继承自AsyncController,因此返回Task或Task的操作方法。有些存储库使用web服务,我们完全可以使用Async/Await来提供可伸缩性优势。在其他情况下,有些可能使用无法安全线程化的非托管服务,在这种情况下,async/await不会提供任何好处。每个存储库都是一个接口IRepository的实现。简

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

在这样的应用程序中实现异步/等待的推荐方法有哪些?为了使模式适合我现有的应用程序,我必须更改接口以返回
Task
但这不是一个更详细的实现细节吗?我可以提供两个方法存根,
GetData
GetDataAsync
,但对我来说,这不允许像Autofac这样的IoC框架具有灵活性,我可以轻松地交换所有内容,而无需更改代码

一位Microsoft Async/Await的开发人员发表了一篇博客文章,“类似这样的问题。如果您的异步方法不是真正的异步(提供本机I/O好处),那么它实际上只会增加开销。换句话说,它不会提供任何可伸缩性好处


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

任务是一个漏洞百出的抽象概念。如果您希望从IO完成端口线程的使用中获益,则必须将任务从存储库一直返回到控制器

尽管使整个调用堆栈返回
任务
可能会提高可伸缩性,但它确实会给应用程序增加一层额外的复杂性。这将阻碍可维护性、可测试性,并使对代码进行推理变得更加困难


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

在您的情况下,我建议将接口设置为异步:

public interface IMyInterface
{
  Task<TMyResult> GetDataAsync();
}
异步方法当然可以是
async
wait

关于实施细节,我会说“是和否”。)在这方面,它与IDisposable
非常相似。从逻辑上讲,这是一个实现细节,从这个意义上讲,它不应该在意以何种方式实现。这不是一个实现细节,因为调用代码需要以不同的方式处理它。因此,从逻辑上讲,我同意这是一个实现细节,但是.NET平台还没有足够的先进性来处理它

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

我有一个解决方案,就是“在大范围内”使用
async
进行设计(即,将
async
与OOP配合使用,而不是允许它发挥功能)。我将解决一些常见问题,如异步初始化和IoC。这只是一个人的意见,但我不知道有什么其他资源可以解决这类问题


另外,在MVC4中,您不再需要从
AsyncController
继承。默认控制器处理程序将根据返回类型(
Task
Task
)自动检测
async
方法。

async
wait
只是将阻塞方法转换为非阻塞方法的抽象。您可以在代码中的任何位置执行此操作,而无需更改接口,除非您希望强制接口方法始终是异步的。如果您在整个调用堆栈中没有任务,则它将永远不会被异步调用。所以你必须把它放在接口中,才能始终返回任务。我不是这么说的。我说的是,通过使用
async
await
可以将任何方法转换为异步方法,包括接口上已有的任何阻塞方法。原始方法上不需要
任务
返回类型。当然,用它们的
async
等价物替换所有接口方法是完全有效的,但这不是必需的。这没有任何好处,实际上增加了开销。查看Stephen Toub关于它的博客文章“我应该为我的同步方法公开一个异步包装器吗?”@CyanLite:如果在整个调用堆栈中不使用Task,代码可以异步工作,但在这种情况下,您将无法从IO完成端口线程中获益,这在你的情况下似乎很关键。我不同意额外的复杂性。使用
async
/
await
(一旦您理解了它们),可维护性、可测试性和代码推理都非常简单。指向您的一系列博客文章的链接似乎已断开。你能更新吗?没关系,Chrome很愚蠢,没有正确地“翻译”链接。谢谢你的发帖。事实上,链接断了;今年早些时候我换了供应商。现在修好了,谢谢!啊,很高兴我提到了什么。好东西,顺便说一句。谢谢你发表博客文章。然而,当涉及到调用异步方法和使用注入对象时,我找不到关于DI/IoC使用的更多信息(不幸的是,注释长度限制不能让我更具体)
public class MyImplementation : IMyInterface
{
  public Task<TMyResult> GetDataAsync()
  {
    TMyResult ret = ...;
    return Task.FromResult(ret);
  }
}