繁体   English   中英

我为什么要返回任务<iactionresult>在 Controller 中?</iactionresult>

[英]Why should I return Task<IActionResult> in a Controller?

因此,我已经尝试掌握了相当长的一段时间,但看不到将每个控制器端点声明为异步方法的意义。

让我们看一个 GET-Request 来可视化这个问题。

这是我通过简单请求到达 go 的方式,只需完成工作并发送响应即可。

[HttpGet]
public IActionResult GetUsers()
{
      // Do some work and get a list of all user-objects.
      List<User> userList = _dbService.ReadAllUsers(); 
      return Ok(userList);
} 

下面是我经常看到的async Task<IActionResult>选项,它与上面的方法相同,但方法本身返回一个Task 有人可能会认为,这个更好,因为您可以有多个请求进入并且它们异步处理但我测试了这种方法和上面的方法,结果相同。 两者都可以同时接受多个请求。 那么我为什么要选择这个签名而不是上面的那个呢? 我只能看到这样的负面影响,例如由于异步而将代码转换为状态机。

[HttpGet]
public async Task<IActionResult> GetUsers()
{
      // Do some work and get a list of all user-objects.
      List<User> userList = _dbService.ReadAllUsers(); 
      return Ok(userList);
} 

下面的这种方法也是我无法理解的。 我看到很多代码都具有这种设置。 他们await然后返回结果的一种async方法。 像这样等待会使代码再次顺序化,而不是获得多任务/多线程的好处。 这个我错了吗?

[HttpGet]
public async Task<IActionResult> GetUsers()
{
      // Do some work and get a list of all user-objects.
      List<User> userList = await _dbService.ReadAllUsersAsync(); 
      return Ok(userList);
} 

如果您能用事实启发我,那就太好了,这样我就可以像现在一样继续发展,或者知道由于误解了这个概念而我做错了。

如果您的数据库服务 class 具有获取用户的async方法,那么您应该会看到好处。 一旦请求发送到数据库,它就会等待另一端服务返回的网络或磁盘响应。 在执行此操作时,可以释放线程来执行其他工作,例如为其他请求提供服务。 在非异步版本中,线程只会阻塞并等待响应。

使用async controller 操作,您还可以获得一个CancellationToken ,如果由于另一端的客户端已终止连接而发出令牌信号,您可以提前退出(但这可能不适用于所有 web 服务器)。

[HttpGet]
public async Task<IActionResult> GetUsers(CancellationToken ct)
{
    ct.ThrowIfCancellationRequested();

    // Do some work and get a list of all user-objects.
    List<User> userList = await _dbService.ReadAllUsersAsync(ct); 
    return Ok(userList);
} 

因此,如果您有一组昂贵的操作并且客户端断开连接,您可以停止处理请求,因为客户端永远不会收到它。 这释放了应用程序的时间和资源来处理客户端对返回结果感兴趣的请求。

但是,如果您有一个不需要async调用的非常简单的操作,那么我可能不会担心它,并保持原样。

请阅读ASP.NET 上的异步/等待简介的“同步与异步请求处理”部分

两者都可以同时接受多个请求。

是的。 这是因为 ASP.NET 是多线程的。 因此,在同步情况下,您只有多个线程调用相同的操作方法(在不同的 controller 实例上)。

对于非多线程平台(例如,Node.js),您必须使代码异步处理同一进程中的多个请求。 但在 ASP.NET 上,它是可选的。

像这样等待会使代码再次顺序化,而不是获得多任务/多线程的好处。

是的,它是顺序的,但它不是同步的。 从某种意义上说,它是顺序的,即async方法一次执行一个语句,并且该请求在async方法完成之前不会完成。 但它不是同步的——同步代码也是顺序的,但它会阻塞一个线程,直到方法完成。

那么我为什么要选择这个签名而不是上面的那个呢?

如果您的后端可以扩展,那么异步操作方法的好处就是可扩展性。 具体来说,异步操作方法在异步操作进行时产生它们的线程 - 在这种情况下, GetUsers在数据库执行其查询时不占用线程。

在测试环境中很难看到好处,因为您的服务器有空闲线程,所以调用异步方法 10 次(占用 0 个线程)和调用同步方法 10 次(占用 10 个线程)之间没有明显的区别,还有 54 个备用)。 您可以人为地限制 ASP.NET 服务器中的线程数,然后进行一些测试以查看差异。

在现实世界的服务器中,您通常希望使其尽可能异步,以便您的线程可用于处理其他请求。 或者,如此所述:

请记住,异步代码不会取代线程池。 这不是线程池异步代码; 它是线程池异步代码。 异步代码允许您的应用程序充分利用线程池。 它采用现有的线程池并将其增加到 11。

请记住上面的“ if ”; 这尤其适用于现有代码。 如果您只有一个 SQL 服务器后端,并且几乎所有操作都查询数据库,那么将它们更改为异步可能没有用,因为可伸缩性瓶颈通常是数据库服务器而不是 web 服务器。 但是,如果您的 web 应用程序可以使用线程来处理非数据库请求,或者如果您的数据库后端是可扩展的(NoSQL、SQL Azure 等,则可能会有所帮助)。

对于新代码,我默认推荐异步方法。 异步可以更好地利用服务器资源并且对云更友好(即,按需付费托管成本更低)。

你在这里缺少一些基本的东西。

当您使用async Task<>时,您实际上是在说“异步运行所有 I/O 代码并释放处理线程以...处理”。

在规模上,您的应用程序将能够每秒处理更多请求,因为您的 I/O 不会占用您的 CPU 密集型工作,反之亦然。

您现在没有看到太多好处,因为您可能正在使用大量资源进行本地测试。

如您所知,ASP.NET 是基于多线程 model 的请求执行。 简而言之,每个请求都在自己的线程中运行,这与其他单线程执行方法(例如 nodejs 中的事件循环)相反。

现在,线程是一种有限资源。 如果您耗尽了线程池(可用线程),您将无法继续有效地处理任务(或者在大多数情况下根本无法)。 如果您对操作处理程序使用同步执行,则您会占用池中的这些线程(即使它们不需要)。 重要的是要了解这些线程处理请求,它们不进行输入/输出。 I/O 由独立于 ASP.NET 请求线程的独立进程处理。 因此,如果在您的操作中,您有一个数据库提取操作需要 15 秒才能执行,那么您将强制当前线程等待空闲15 秒以返回数据库结果,以便它可以继续执行代码。 因此,如果您有 50 个这样的请求,那么您将在基本睡眠模式下占用 50 个线程。 显然,这不是非常可扩展的,并且很快就会成为一个问题。 为了有效地使用您的资源,最好在到达 I/O 操作时保留正在执行的请求的 state,同时释放线程以处理另一个请求。 当 I/O 操作完成时,state 被重新组装并提供给池中的空闲线程以恢复处理。 这就是您使用异步处理的原因。 它可以让您更有效地使用有限资源并防止此类线程饥饿。 当然,这不是最终的解决方案。 它可以帮助您扩展应用程序以获得更高的负载。 如果您不需要它,请不要使用它,因为它只会增加开销。

异步操作返回一个任务(它不是结果,而是一个 promise,一旦任务完成就会有结果。

异步方法应该是await ed,以便它等待任务完成。 如果您await异步方法,则返回值将不再是任务,而是您预期的结果。

想象一下这个异步方法:

async Task<SomeResult> SomeMethod()
{
    ...
} 

以下代码:

var result = SomeMethod(); // the result is a Task<SomeResult> which may have not completed yet

var result = await SomeMethod(); // the result is type of SomeResult

现在,为什么我们使用async方法而不是同步方法:

想象一下,一个方法正在做一些可能需要很长时间才能完成的工作,当它完成同步时,所有其他请求将等待直到这个耗时工作的执行完成,因为它们都发生在同一个线程中。 但是,当它是异步的时,任务将在一个(可能是另一个)线程中完成,并且不会阻塞其他请求。

结果从一个异步(非阻塞)操作返回一个任务,该操作代表一些应该完成的工作。 任务可以告诉您工作是否已完成,如果操作返回结果,则任务会为您提供在任务完成之前不可用的结果。 您可以从官方 Microsoft Docs 了解有关 C# 异步编程的更多信息:

https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=net-5.0

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM