【问题标题】:What happens while waiting on a Task's Result?等待任务结果时会发生什么?
【发布时间】:2012-09-11 03:16:19
【问题描述】:

我正在使用 HttpClient 将数据发布到 .NET 4.0 项目中的远程服务。我不关心这个操作阻塞,所以我想我可以跳过 ContinueWith 或 async/await 并使用 Result。

在调试时,我遇到了远程服务器没有响应的问题。当我单步执行代码时,我的代码似乎刚刚在第三行停止运行......当前堆栈指针行停止以黄色突出显示,并且没有前进到下一行。它就这样消失了。我花了一段时间才意识到我应该等待请求超时。

var client = new HttpClient();
var task = client.PostAsync("http://someservice/", someContent);
var response = task.Result;

我的理解是,在 Task 上调用 Result 会导致代码同步执行,表现得更像这样(我知道 HttpClient 中没有 Post 方法):

var client = new HttpClient();
var response = client.Post("http://someservice/", someContent);

我不确定这是一件坏事,我只是想弄清楚它。由于 HttpClient 正在返回任务而不是直接返回结果这一事实,我的应用程序是否真的会自动利用异步,即使我认为我正在避免它?

【问题讨论】:

  • 文档说:“此属性的 get 访问器确保异步操作在返回之前完成。”所以你的假设是正确的。但是,也许你会遇到因为操作失败而导致 Task 抛出异常的情况。
  • 我猜是堆栈指针消失了。我没有意识到如果我等待的时间足够长,阻塞的 Result 调用就会异常。看起来好像代码在方法执行到一半时就停止了。也许这应该是一个 VS Connect 类型的问题,要求提供一些 UI 提示,即第三行有一个异步操作挂起。我认为调试器甚至将我带回了调用类。
  • 有完整源代码的最终解决方案吗?

标签: c# asynchronous task


【解决方案1】:

在 Windows 中,所有 I/O 都是异步的。同步 API 只是一种方便的抽象。

所以,当您使用HttpWebRequest.GetResponse 时,实际发生的是 I/O 启动(异步),调用线程(同步)阻塞,等待它完成。

同样,当您使用HttpClient.PostAsync(..).Result 时,I/O 会(异步)启动,调用线程(同步)会阻塞,等待它完成。

我通常建议人们使用await 而不是Task.ResultTask.Wait,原因如下:

  1. 如果您阻止 Task(这是 async 方法的结果),您可以 easily get into a deadlock situation
  2. Task.ResultTask.Wait 将所有异常包装在 AggregateException 中(因为这些 API 是 TPL 的保留)。所以错误处理更加复杂。

但是,如果您了解这些限制,则在某些情况下阻止 Task 可能会很有用(例如,在控制台应用程序的 Main 中)。

【讨论】:

  • 那么结论是如果你的代码是同步的就不用HttpClient了?这不是有点 .NET 设计的味道吗?除非我们使整个调用堆栈异步,否则我们不能使用异步方法。我希望等待异步方法完成以完成同步操作将是异步等待的最常见用法。
  • @Vukoje:自然异步操作(包括所有 I/O)应该由异步 API 表示。使用它们的正确方法是异步的,而不是同步的。如果开发人员“需要”同步使用异步 API,那么这始终表明使用应用程序设计错误(有时由于框架设计错误而不可避免,但通常只是应用程序设计错误)。
  • 感谢您的澄清!只是为了确保我完全理解,如果我有不想进行异步的 WebAPI 方法,我不应该使用 HttpClient.PostAsync 吗?如果在那个 WebAPI 方法(不是异步)中我想要两个并行的 HTTP 帖子怎么办?在那种情况下,我应该结合使用两个 PostAsync() 调用与 Task.WaitAll 还是这种方法也不好。
  • @Vukoje:如果您的 WebAPI 方法正在执行 HTTP 请求,它应该是异步的。如果您出于某种原因选择使其同步,那么您可以将每个(同步)POST 调用包装在 Task.Run 中。
  • 非常感谢您的努力,对我帮助很大!我仍然不明白为什么我要让 WebApi 方法异步。它增加了一些复杂性,我通过阅读发现的唯一好处是它可能有利于 Web 服务器在重负载下的响应时间。
【解决方案2】:

捕获任务的结果会阻塞当前线程。在这种情况下,使用异步版本的方法是没有意义的。 Post()PostAsync().Result 都会阻塞。

如果你想使用并发,你应该这样写:

async Task PostContent()
{
  var client = new HttpClient();
  Task t = await client.PostAsync("http://someservice/", someContent);
  //code after this line will execute when the PostAsync completes.
  return t;
}

由于PostContent() 本身返回一个Task,调用它的方法也应该等待。

async void ProcessResult()
{
   var result = await PostContent(); 
   //Do work with the result when the result is ready 
}

例如,如果您在按钮单击处理程序中调用 ProcessResult(),您会看到 UI 仍然是响应式的,其他控件仍然起作用。

【讨论】:

    猜你喜欢
    • 2016-01-10
    • 2014-11-22
    • 2019-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多