当使用异步web服务时,应该“等待”什么?什么是不应该做的?
本文关键字:什么 等待 不应该 应该 异步 web 服务 | 更新日期: 2023-09-27 18:04:20
我正在开发一个MVC web应用程序,它允许我通过web服务异步管理我的数据。
我的理解是,这允许访问该网站运行的服务器的应用程序池的CPU线程在发出请求后返回到应用程序池,以便它们可以用于服务其他请求而不会使整个线程停滞。
假设我的理解是正确的(尽管它可能是糟糕的措辞),我开始思考什么时候我应该await
的事情。考虑下面的函数:
public async Task<ActionResult> Index()
{
List<User> users = new List<User>();
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:41979");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync("api/user/");
if (response.IsSuccessStatusCode)
{
users = await response.Content.ReadAsAsync<List<User>>();
}
}
return View(users);
}
我所有的函数看起来都很相似,除了它们用web服务返回的数据做不同的事情,我想知道,我是否也应该等待返回?
类似:
return await View(users);
或
await return View(users);
我的意思是,到目前为止,网站运行得很好,除了我对web服务应该发送回客户端网站的确切内容有点困惑,但由于我是开发web服务的新手,我仍然想知道我是否在正确地做事情,这已经在我身上吃了一段时间。
您只能等待通过Task
, Task<T>
或自定义awaiter公开异步操作的命名或匿名方法和lambda表达式。由于View
本身不做任何异步操作,因此您不能等待它。
使用await
的实际意义是什么?通常,你有一些IO绑定操作,本质上是异步的。通过使用异步API,您可以通过返回线程池来允许线程不被阻塞,并利用它来服务不同的请求。async
不改变HTTP的本质。它仍然是"请求-响应"。当async
方法产生控制时,它不会向客户端返回响应。它只会在操作完成后返回。
View(object obj)
返回一个ViewResult
,它将把您的对象转换为所需的输出。ViewResult
不是可等待的,因为它没有通过可等待对象公开任何"承诺"。因此,您不能异步地等待它。
我开始考虑什么时候该等东西
最好总是等待异步调用的结果。
如果你不等待,你火了&忘记,在成功和错误情况下,您都不会在您的端收到响应。
当您需要将异步任务作为值展开时,您可以"等待"。否则,您可以返回一个任务,并在需要时稍后运行它。还要注意. wait()与await不同。Wait()将阻塞直到任务完成(旁注我知道)。还要检查你的代码,你的方法签名中有语法错误:
public async <Task>ActionResult> Index()
应该public async Task<ActionResult> Index()
我认为这是一个非常好的问题,但也很难回答,尤其是在一个网站的情况下。
我对web服务应该发送回客户端网站的内容有点困惑
最重要的是要理解如果你使用async/await,那么你的动作方法代码仍然是序列化的,我的意思是下一行将只在async操作完成后执行。但是会有(至少)三个线程:
- action方法所在的原始web服务器工作线程调用。最初MVC基础架构从池,并将此线程专用于服务当前请求。
- 可选的线程是由异步操作启动的用await调用。
- 一个延续线程(也是来自池)中,您的操作方法在等待之后继续。请注意这个线程将具有相同的上下文(HttpContext,用户文化)原来的worker是什么,所以对你来说是透明的,但它将是从池中新分配的另一个线程。
乍一看,它没有任何意义:如果action方法中的操作仍然是序列化的,为什么所有这些线程都被占用?我花了同样的时间……要理解这一点,我们必须看一下桌面应用程序,比如WPF应用程序。
简而言之:有一个消息循环和一个专用线程,它从队列中读取UI(和其他虚拟)事件,并在该线程的上下文中执行事件处理程序,如按钮单击事件。如果你阻塞这个线程2秒,UI将在这段时间内无响应。因此,这就是为什么我们希望在另一个线程中以异步方式做许多事情,并让专用(UI)线程快速返回并处理队列中的其他消息。然而,这可能非常不方便,因为有时我们希望在异步操作结束后继续一些东西,并且我们已经得到了结果。c#的async/await特性使得这非常方便。在这种情况下,延续线程始终是专用的UI线程,但在其无限循环的(非常)后一轮。总结一下:
在事件处理程序中,你可以使用async/await来执行你的序列化操作,延续将始终在原始的专用UI线程中完成,但在等待期间,该线程可以循环,并处理其他消息。
现在回到MVC操作方法:在这种情况下,您的操作方法类似于事件处理程序。尽管仍然很难理解线程复杂性的使用:在这种情况下,阻塞action方法不会阻塞任何东西(因为它阻塞了WPF中的专用UI线程)。以下是事实:
- 其他客户端(浏览器)请求将由其他线程处理。(原来如此不阻塞任何阻塞这个线程(*)继续阅读
- 这个请求不会被更快地服务,即使我们使用async/await,因为操作被序列化,我们必须等待(await)的结果异步操作。(不要混淆async/await和并行处理)
所以async/await和透明线程技巧的使用并不像在桌面应用程序中那么明显。
Still: Pool不是一个无穷无尽的资源。可能可以更好地利用线程池,可能可以在极端压力下可以使web服务器的性能更好。