简体   繁体   English

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

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

So I have been trying to get the grasp for quite some time now but couldn't see the sense in declaring every controller-endpoint as an async method.因此,我已经尝试掌握了相当长的一段时间,但看不到将每个控制器端点声明为异步方法的意义。

Let's look at a GET-Request to visualize the question.让我们看一个 GET-Request 来可视化这个问题。

This is my way to go with simple requests, just do the work and send the response.这是我通过简单请求到达 go 的方式,只需完成工作并发送响应即可。

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

Below is the async Task<IActionResult> option I see very often, it does the same as the method above but the method itself is returning a Task .下面是我经常看到的async Task<IActionResult>选项,它与上面的方法相同,但方法本身返回一个Task One could think, that this one is better because you can have multiple requests coming in and they get worked on asynchronously BUT I tested this approach and the approach above with the same results.有人可能会认为,这个更好,因为您可以有多个请求进入并且它们异步处理但我测试了这种方法和上面的方法,结果相同。 Both can take multiple requests at once.两者都可以同时接受多个请求。 So why should I choose this signature instead of the one above?那么我为什么要选择这个签名而不是上面的那个呢? I can only see the negative effects of this like transforming the code into a state-machine due to being async.我只能看到这样的负面影响,例如由于异步而将代码转换为状态机。

[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);
} 

This approach below is also something I don't get the grasp off.下面的这种方法也是我无法理解的。 I see a lot of code having exactly this setup.我看到很多代码都具有这种设置。 One async method they await and then returning the result.他们await然后返回结果的一种async方法。 Awaiting like this makes the code sequential again instead of having the benefits of Multitasking/Multithreading.像这样等待会使代码再次顺序化,而不是获得多任务/多线程的好处。 Am I wrong on this one?这个我错了吗?

[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);
} 

It would be nice if you could enlighten me with facts so I can either continue developing like I do right now or know that I have been doing it wrong due to misunderstanding the concept.如果您能用事实启发我,那就太好了,这样我就可以像现在一样继续发展,或者知道由于误解了这个概念而我做错了。

If your DB Service class has an async method for getting the user then you should see benefits.如果您的数据库服务 class 具有获取用户的async方法,那么您应该会看到好处。 As soon as the request goes out to the DB then it is waiting on a network or disk response back from the service at the other end.一旦请求发送到数据库,它就会等待另一端服务返回的网络或磁盘响应。 While it is doing this the thread can be freed up to do other work, such as servicing other requests.在执行此操作时,可以释放线程来执行其他工作,例如为其他请求提供服务。 As it stands in the non-async version the thread will just block and wait for a response.在非异步版本中,线程只会阻塞并等待响应。

With async controller actions you can also get a CancellationToken which will allow you to quit early if the token is signalled because the client at the other end has terminated the connection (but that may not work with all web servers).使用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);
} 

So, if you have an expensive set of operations and the client disconnects, you can stop processing the request because the client won't ever receive it.因此,如果您有一组昂贵的操作并且客户端断开连接,您可以停止处理请求,因为客户端永远不会收到它。 This frees up the application's time and resources to deal with requests where the client is interested in a result coming back.这释放了应用程序的时间和资源来处理客户端对返回结果感兴趣的请求。

However, if you have a very simple action that requires no async calls then I probably wouldn't worry about it, and leave it as it is.但是,如果您有一个不需要async调用的非常简单的操作,那么我可能不会担心它,并保持原样。

Please read the "Synchronous vs. Asynchronous Request Handling" section of Intro to Async/Await on ASP.NET .请阅读ASP.NET 上的异步/等待简介的“同步与异步请求处理”部分

Both can take multiple requests at once.两者都可以同时接受多个请求。

Yes.是的。 This is because ASP.NET is multithreaded.这是因为 ASP.NET 是多线程的。 So, in the synchronous case, you just have multiple threads invoking the same action method (on different controller instances).因此,在同步情况下,您只有多个线程调用相同的操作方法(在不同的 controller 实例上)。

For non-multithreaded platforms (eg, Node.js), you have to make the code asynchronous to handle multiple requests in the same process.对于非多线程平台(例如,Node.js),您必须使代码异步处理同一进程中的多个请求。 But on ASP.NET it's optional.但在 ASP.NET 上,它是可选的。

Awaiting like this makes the code sequential again instead of having the benefits of Multitasking/Multithreading.像这样等待会使代码再次顺序化,而不是获得多任务/多线程的好处。

Yes, it is sequential , but it's not synchronous .是的,它是顺序的,但它不是同步的。 It's sequential in the sense that the async method executes one statement at a time, and that request isn't complete until the async method completes.从某种意义上说,它是顺序的,即async方法一次执行一个语句,并且该请求在async方法完成之前不会完成。 But it's not synchronous - the synchronous code is also sequential, but it blocks a thread until the method completes.但它不是同步的——同步代码也是顺序的,但它会阻塞一个线程,直到方法完成。

So why should I choose this signature instead of the one above?那么我为什么要选择这个签名而不是上面的那个呢?

If your backend can scale, then the benefit of asynchronous action methods is scalability.如果您的后端可以扩展,那么异步操作方法的好处就是可扩展性。 Specifically, asynchronous action methods yield their thread while the asynchronous operation is in progress - in this case, GetUsers is not taking up a thread while the database is performing its query.具体来说,异步操作方法在异步操作进行时产生它们的线程 - 在这种情况下, GetUsers在数据库执行其查询时不占用线程。

The benefit can be hard to see in a testing environment, because your server has threads to spare, so there's no observable difference between calling an asynchronous method 10 times (taking up 0 threads) and calling a synchronous method 10 times (taking up 10 threads, with another 54 to spare).在测试环境中很难看到好处,因为您的服务器有空闲线程,所以调用异步方法 10 次(占用 0 个线程)和调用同步方法 10 次(占用 10 个线程)之间没有明显的区别,还有 54 个备用)。 You can artificially restrict the number of threads in your ASP.NET server and then do some tests to see the difference.您可以人为地限制 ASP.NET 服务器中的线程数,然后进行一些测试以查看差异。

In a real-world server, you usually want to make it as asynchronous as possible so that your threads are available for handling other requests.在现实世界的服务器中,您通常希望使其尽可能异步,以便您的线程可用于处理其他请求。 Or, as described here :或者,如此所述:

Bear in mind that asynchronous code does not replace the thread pool.请记住,异步代码不会取代线程池。 This isn't thread pool or asynchronous code;这不是线程池异步代码; it's thread pool and asynchronous code.它是线程池异步代码。 Asynchronous code allows your application to make optimum use of the thread pool.异步代码允许您的应用程序充分利用线程池。 It takes the existing thread pool and turns it up to 11.它采用现有的线程池并将其增加到 11。

Bear in mind the " if " above;请记住上面的“ if ”; this particularly applies to existing code.这尤其适用于现有代码。 If you have just a single SQL server backend, and if pretty much all your actions query the db, then changing them to be asynchronous may not be useful , since the scalability bottleneck is usually the db server and not the web server.如果您只有一个 SQL 服务器后端,并且几乎所有操作都查询数据库,那么将它们更改为异步可能没有用,因为可伸缩性瓶颈通常是数据库服务器而不是 web 服务器。 But if your web app could use threads to handle non-db requests, or if your db backend is scalable (NoSQL, SQL Azure, etc), then changing action methods to be asynchronous would likely help.但是,如果您的 web 应用程序可以使用线程来处理非数据库请求,或者如果您的数据库后端是可扩展的(NoSQL、SQL Azure 等,则可能会有所帮助)。

For new code, I recommend asynchronous methods by default.对于新代码,我默认推荐异步方法。 Asynchronous makes better use of server resources and is more cloud-friendly (ie, less expensive for pay-as-you-go hosting).异步可以更好地利用服务器资源并且对云更友好(即,按需付费托管成本更低)。

You are missing something fundamental here.你在这里缺少一些基本的东西。

When you use async Task<> you are effectively saying, "run all the I/O code asynchronously and free up processing threads to... process".当您使用async Task<>时,您实际上是在说“异步运行所有 I/O 代码并释放处理线程以...处理”。

At scale, your app will be able to serve more requests/second because your I/O is not tying up your CPU intensive work and vice versa.在规模上,您的应用程序将能够每秒处理更多请求,因为您的 I/O 不会占用您的 CPU 密集型工作,反之亦然。

You're not seeing much benefit now, because you're probably testing locally with plenty of resources at hand.您现在没有看到太多好处,因为您可能正在使用大量资源进行本地测试。

See

As you know, ASP.NET is based on a multi-threaded model of request execution.如您所知,ASP.NET 是基于多线程 model 的请求执行。 To put it simply, each request is run in its own thread, contrary to other single thread execution approaches like the event-loop in nodejs.简而言之,每个请求都在自己的线程中运行,这与其他单线程执行方法(例如 nodejs 中的事件循环)相反。

Now, threads are a finite resource.现在,线程是一种有限资源。 If you exhaust your thread pool (available threads) you can't continue handling tasks efficiently (or in most cases at all).如果您耗尽了线程池(可用线程),您将无法继续有效地处理任务(或者在大多数情况下根本无法)。 If you use synchronous execution for your action handlers, you occupy those threads from the pool (even if they don't need to be).如果您对操作处理程序使用同步执行,则您会占用池中的这些线程(即使它们不需要)。 It is important to understand that those threads handle requests, they don't do input/output.重要的是要了解这些线程处理请求,它们不进行输入/输出。 I/O is handled by separate processes which are independent of the ASP.NET request threads. I/O 由独立于 ASP.NET 请求线程的独立进程处理。 So, if in your action you have a DB fetch operation that takes let's say 15 seconds to execute, you're forcing your current thread to wait idle 15 seconds for the DB result to be returned so it can continue executing code.因此,如果在您的操作中,您有一个数据库提取操作需要 15 秒才能执行,那么您将强制当前线程等待空闲15 秒以返回数据库结果,以便它可以继续执行代码。 Therefore, if you have 50 such requests, you'll have 50 threads occupied in basically sleep mode.因此,如果您有 50 个这样的请求,那么您将在基本睡眠模式下占用 50 个线程。 Obviously, this is not very scalable and becomes a problem soon enough.显然,这不是非常可扩展的,并且很快就会成为一个问题。 In order to use your resources efficiently, it is a good idea to preserve the state of the executing request when an I/O operation is reached and free the thread to handle another request in the meantime.为了有效地使用您的资源,最好在到达 I/O 操作时保留正在执行的请求的 state,同时释放线程以处理另一个请求。 When the I/O operation completes, the state is reassembled and given to a free thread in the pool to resume the handling.当 I/O 操作完成时,state 被重新组装并提供给池中的空闲线程以恢复处理。 That's why you use async handling.这就是您使用异步处理的原因。 It lets you use your finite resources more efficiently and prevents such thread starvation.它可以让您更有效地使用有限资源并防止此类线程饥饿。 Of course, it is not an ultimate solution.当然,这不是最终的解决方案。 It helps you scale your application for higher load.它可以帮助您扩展应用程序以获得更高的负载。 If you don't have the need for it, don't use it as it just adds overhead.如果您不需要它,请不要使用它,因为它只会增加开销。

Async operations return a Task(it is not the result, but a promise made that will have the results once the task is completed.异步操作返回一个任务(它不是结果,而是一个 promise,一旦任务完成就会有结果。

The async methods should be await ed so that it waits till the task is completed.异步方法应该是await ed,以便它等待任务完成。 if you await an async method, the return values won't be a task anymore and will be the results you expected.如果您await异步方法,则返回值将不再是任务,而是您预期的结果。

Imagine this async method:想象一下这个异步方法:

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

the following code:以下代码:

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

Now, why do we use async methods instead of sync methods:现在,为什么我们使用async方法而不是同步方法:

Imagine that a Method is doing some job that may take a long time to complete, when it is done sync all other requests will be waiting till the execution of this long taking job to complete, as they are all happening in the same thread.想象一下,一个方法正在做一些可能需要很长时间才能完成的工作,当它完成同步时,所有其他请求将等待直到这个耗时工作的执行完成,因为它们都发生在同一个线程中。 However when it is async, the task will be done in a (possibly another) thread and will not block the other requests.但是,当它是异步的时,任务将在一个(可能是另一个)线程中完成,并且不会阻塞其他请求。

The result returns a task from an asynchronous (non-blocking) operation which represents some work that should be done.结果从一个异步(非阻塞)操作返回一个任务,该操作代表一些应该完成的工作。 The task can tell you if the work is completed and if the operation returns a result, the task gives you the result which won't be available until the task is completed.任务可以告诉您工作是否已完成,如果操作返回结果,则任务会为您提供在任务完成之前不可用的结果。 You can learn more about C# asynchronous programming from the official Microsoft Docs:您可以从官方 Microsoft Docs 了解有关 C# 异步编程的更多信息:

https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=net-5.0 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