最近,我对我在Web API项目中实现async-await模式的方式产生了怀疑。我已经读过async-await应该是“一直”,这就是我所做的。但这一切都开始显得多余,我不确定我是否正确地做到了这一点。我有一个调用存储库的控制器,它调用数据访问(实体框架6)类 - “异步一直”。我已经阅读了很多相互矛盾的内容,并希望将其清理干净。
编辑:引用的可能重复是一个很好的帖子,但不足以满足我的需求。我提供了代码来说明问题。看来真的很难得到一个决定性的答案。如果我们可以将async-await放在一个地方然后让.net处理其余部分,那就太好了,但我们做不到。那么,我是在做这件事还是不那么简单。
这就是我所拥有的:
控制器:
public async Task<IHttpActionResult> GetMessages()
{
var result = await _messageRepository.GetMessagesAsync().ConfigureAwait(false);
return Ok(result);
}
存储库:
public async Task<List<string>> GetMessagesAsync()
{
return await _referralMessageData.GetMessagesAsync().ConfigureAwait(false);
}
数据:
public async Task<List<string>> GetMessagesAsync()
{
return await _context.Messages.Select(i => i.Message).ToListAsync().ConfigureAwait(false);
}
答案 0 :(得分:9)
如果我们可以将async-await放在一个地方然后让.net处理其余部分,那将是很好的,但我们做不到。那么,我是在做这件事还是不那么简单。
如果它更简单, 会很好。
示例存储库和数据代码中没有太多真实的逻辑(并且在await
之后没有),因此可以简化它们以直接返回任务,正如其他评论者所指出的那样。
另一方面,示例存储库遇到了常见的存储库问题:什么都不做。如果您的真实存储库的其余部分类似,那么您的系统中可能有一个抽象级别太多。请注意,实体框架已经是一个通用的工作单元存储库。
但是对于一般情况下的async
和await
,代码通常需要在await
后执行:
public async Task<IHttpActionResult> GetMessages()
{
var result = await _messageRepository.GetMessagesAsync();
return Ok(result);
}
请记住,async
和await
只是用于连接回调的精彩语法。没有一种更简单的方法可以异步表达此方法的逻辑。已经进行了一些实验,例如推断await
,但此时它们都被丢弃了(我有一篇描述why the async
/await
keywords have all the "cruft" that they do的博客文章。)
这种方法对每种方法都是必要的。使用async
/ await
的每种方法都在建立自己的回调。如果回调不是,那么该方法可以直接返回任务,避免async
/ await
。其他异步系统(例如,JavaScript中的promise)具有相同的限制:它们必须一直是异步的。
从概念上讲,可以定义一个系统,在该系统中,任何阻塞操作都会自动生成线程。我反对这样一个系统的最重要的论点是它会有隐含的重入。特别是在考虑第三方库更改时,自动生成系统将是不可维护的IMO。在其签名中明确API的异步性更好(即,如果它返回Task
,那么它是异步的)。
现在,@ usr提出了一个很好的观点,也许你根本不需要异步 。例如,如果您的实体框架代码正在查询单个SQL Server实例,则几乎可以肯定。这是因为async
对ASP.NET的主要好处是可伸缩性,如果您不需要可伸缩性(ASP.NET部分),那么您不需要异步。请参阅async
ASP.NET上的MSDN文章中的“不是银弹”部分。
但是,我认为还有一个争论是“天然API”。如果操作自然是异步的(例如,基于I / O),则其最自然的API是异步API。相反,自然同步操作(例如,基于CPU)最自然地表示为同步API。对于库来说,自然的API参数是最强的 - 如果您的存储库/数据访问层是其自己的dll,旨在在其他(可能是桌面或移动)应用程序中重用,那么它绝对应该是一个异步API。但是,如果(更可能是这种情况)它特定于这个不需要扩展的ASP.NET应用程序,那么就没有特别需要使API成为异步或同步。
但是有一个关于开发者体验的双管齐下的反驳论点。许多开发人员根本不知道他们的方式async
;代码维护者会不会搞砸它?该论点的另一个要点是围绕async
的库和工具仍然仍然正在加速。最值得注意的是,当存在追踪异常的情况时缺少因果关系堆栈(在旁注中,我写了library来帮助解决这个问题)。此外,ASP.NET的部分内容不是async
兼容的 - 最值得注意的是,MVC过滤器和子操作(他们正在使用ASP.NET vNext修复这两者)。 ASP.NET对于异步处理程序的超时和线程中止有不同的行为 - 在async
学习曲线上添加了一些更多。
当然,反柜台的论点是对正在开发的开发人员的正确回应是训练他们,而不是限制可用的技术。
简而言之:
async
的正确方法是“一路走来”。在ASP.NET上尤其如此,它不太可能很快改变。async
是否合适或有用,取决于您和您的应用程序的情况。答案 1 :(得分:6)
public async Task<List<string>> GetMessagesAsync()
{
return await _referralMessageData.GetMessagesAsync().ConfigureAwait(false);
}
public async Task<List<string>> GetMessagesAsync()
{
return await _context.Messages.Select(i => i.Message).ToListAsync().ConfigureAwait(false);
}
如果你对异步方法的唯一调用是尾调用,那么你真的不需要await
:
public Task<List<string>> GetMessagesAsync()
{
return _referralMessageData.GetMessagesAsync();
}
public Task<List<string>> GetMessagesAsync()
{
return _context.Messages.Select(i => i.Message).ToListAsync();
}
关于你唯一丢失的是一些堆栈跟踪信息,但这些信息很少有用。删除await
然后生成一个处理等待的状态机,而不是将被调用方法生成的任务传递给调用方法,调用方法可以await
。< / p>
这些方法现在有时也可以内联,或者可能对它们进行尾调用优化。
如果执行此操作相对简单,我甚至可以将基于任务的非路径转换为基于任务的路径:
public async Task<List<string>> GetMeesagesAsync()
{
if(messageCache != null)
return messageCache;
return await _referralMessageData.GetMessagesAsync().ConfigureAwait(false);
}
变为:
public Task<List<string>> GetMeesagesAsync()
{
if(messageCache != null)
return Task.FromResult(messageCache);
return _referralMessageData.GetMessagesAsync();
}
但是,如果您在任何时候需要执行任务的结果,那么await
就可以了。