【问题标题】:When working with asynchronous web services, what should "await" and what shouldn't?使用异步 Web 服务时,什么应该“等待”,什么不应该?
【发布时间】:2015-07-23 13:28:02
【问题描述】:

我正在开发一个 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 服务的开发新手,我仍然想知道我做事是否正确,这已经困扰我一段时间了。

【问题讨论】:

    标签: c# multithreading web-services asynchronous async-await


    【解决方案1】:

    您只能等待通过TaskTask&lt;T&gt; 或自定义等待程序公开异步操作的命名或匿名方法和 lambda 表达式。由于View 本身不执行任何异步操作,因此您不能等待它。

    使用await 的真正意义是什么?通常,您有一些本质上是异步的 IO 绑定操作。通过使用异步 API,您可以通过返回线程池来允许线程不受阻塞,从而利用它来服务不同的请求。 async 不会改变 HTTP 的性质。它仍然是“请求-响应”。当async 方法产生控制权时,它不会向客户端返回响应。它只会在操作完成后返回。

    View(object obj) 返回一个ViewResult,这反过来会将您的对象转换为所需的输出。 ViewResult 不可等待,因为它不会通过可等待对象公开任何“承诺”。因此,您不能异步等待它。

    【讨论】:

      【解决方案2】:

      我开始考虑什么时候应该等待

      最好总是等待异步调用的结果。

      如果您不等待,您解雇并忘记,您在成功和错误情况下都不会收到响应。

      【讨论】:

      • 这并不完全正确。如果您开火而忘记了任务,并且您在任务中有错误,您将陷入僵局。如果您想真正触发并忘记,请在您的任务上使用 .ConfigureAwait(false) ,这样它就不会尝试返回同步上下文。
      • 这个答案和斯蒂芬的评论都包含错误信息。如果您从不等待 ConfigureAwait 返回的对象,则执行 .ConfigureAwait(false) 的效果为 0。此外,您永远不应该在 ASP.NET 环境中执行 Fire and forget,因为当 AppDomain 在完成请求服务后关闭并且在应用程序池回收超时之前没有新请求进入时,后台任务可以并且将会中止其线程。
      • ...您必须在 4.5.2 上使用 HostingEnvironment.QueueBackgroundWorkItem 或在 4.5.2 之前使用 3rd party library 才能避免提前结束任务。
      • @ScottChamberlain 我正在阅读这些答案和 cmets,但我只是觉得我不理解其中任何一个:/
      • @Scott Chamberlain:抱歉,我没听懂你说的。您能否详细说明或指向互联网上的链接?您对此有何看法:stackoverflow.com/questions/22864367/fire-and-forget-approach
      【解决方案3】:

      当您需要将异步任务解包为值时,您“等待”。否则,您可以返回一个任务,并在需要时让它在以后运行。另请注意,.Wait() 与 await 不同。 Wait() 将阻塞,直到任务完成(我知道的旁注)。还要检查您的代码,您的方法签名中有语法错误:

      public async <Task>ActionResult> Index()
      

      应该是

      public async Task<ActionResult> Index()
      

      【讨论】:

        【解决方案4】:

        我认为这是一个很好的问题,但也很难回答,尤其是在网站的情况下。

        我对 Web 服务应该向客户端网站发送回什么内容感到有些困惑'

        最重要的是要理解如果你使用async/await,那么你的action方法代码仍然是序列化的,我的意思是只有在async操作完成后才会执行下一行。但是会有(至少)三个线程:

        • 到原来的 web 服务器工作线程,其中 action 方法是 叫。最初 MVC 基础设施从 池,并将此线程专用于服务当前请求。
        • 异步操作启动的可选线程 用 await 调用。
        • 一个延续线程(也是来自 池),您的操作方法在等待之后继续。笔记 该线程将具有相同的上下文(HttpContext,用户文化 等)原来的工作线程是什么,所以它对你来说是透明的,但它将是从池中新分配的另一个线程。

        乍一看没有任何意义:如果action方法中的操作仍然是序列化的,为什么所有这些线程hocus-pocus?我需要同样的时间...要理解这一点,我们必须看一下桌面应用程序,比如 WPF 应用程序。

        简而言之:有一个消息循环和一个专用线程,它从队列中读取 UI(和其他虚拟)事件并在该线程的上下文中执行事件处理程序,例如按钮单击事件。如果您将此线程阻塞 2 秒,则 UI 将在这段时间内无响应。所以这就是为什么我们希望在另一个线程中以异步方式做很多事情,并让专用(UI)线程快速返回和处理队列中的其他消息。然而,这可能非常不方便,因为我们有时想在异步操作结束并且得到结果后继续。 C# 特性 async/await 使这非常方便。在这种情况下,延续线程始终是专用的 UI 线程,但在其(非常)后面的循环中处于无限循环中。总结一下:

        在事件处理程序中,您可以使用 async/await 仍然以序列化方式执行您的操作,继续将始终在原始专用 UI​​ 线程中完成,但在 await 期间,此线程可以循环并处理其他消息。

        现在回到 MVC 操作方法:在这种情况下,您的操作方法类似于事件处理程序。尽管仍然很难理解线程复杂性的使用:这种情况下阻塞 action 方法不会阻塞任何东西(因为它阻塞了 WPF 中的专用 UI​​ 线程)。以下是事实:

        • 其他客户端(浏览器)请求将由其他线程处理。 (所以我们是 不通过阻塞这个线程来阻塞任何东西 (*) 继续阅读
        • 即使我们使用 async/await,也不会更快地处理此请求,因为 操作是序列化的,我们必须等待(等待)结果 异步操作。 (不要将 async/await 与并行处理混淆)

        因此,async/await 和透明线程技巧的使用不像桌面应用程序那样明显。

        仍然:池不是无穷无尽的资源。尽快将工作线程释放回池(通过等待)最初专用于服务请求的工作线程,并在完成异步操作后在另一个线程中继续可能可以更好地利用线程池和可能在极端压力下提高网络服务器的性能。

        【讨论】:

          猜你喜欢
          • 2019-01-28
          • 2019-10-28
          • 2010-09-17
          • 1970-01-01
          • 2011-09-25
          • 2011-01-20
          • 1970-01-01
          • 1970-01-01
          • 2021-12-30
          相关资源
          最近更新 更多