gpt4 book ai didi

c# - HttpClient.GetAsync(...) 在使用 await/async 时永远不会返回

转载 作者:IT王子 更新时间:2023-10-29 03:28:21 25 4
gpt4 key购买 nike

编辑: This question看起来它可能是同样的问题,但没有回应......

编辑:在测试用例 5 中,任务似乎卡在 WaitingForActivation 中状态。

我在 .NET 4.5 中使用 System.Net.Http.HttpClient 遇到了一些奇怪的行为 - 其中“等待”调用(例如)的结果 httpClient.GetAsync(...)永远不会回来。

这仅在使用新的 async/await 语言功能和 Tasks API 时发生在某些情况下 - 代码似乎总是在仅使用延续时工作。

这是一些重现问题的代码 - 将其放入 Visual Studio 11 中的新“MVC 4 WebApi 项目”中以公开以下 GET 端点:

/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6

除了 /api/test5 之外,这里的每个端点都返回相同的数据(来自 stackoverflow.com 的响应 header )。永远不会完成。

我是否在 HttpClient 类中遇到了错误,或者我是否以某种方式滥用了 API?

复制代码:
public class BaseApiController : ApiController
{
/// <summary>
/// Retrieves data using continuations
/// </summary>
protected Task<string> Continuations_GetSomeDataAsync()
{
var httpClient = new HttpClient();

var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);

return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
}

/// <summary>
/// Retrieves data using async/await
/// </summary>
protected async Task<string> AsyncAwait_GetSomeDataAsync()
{
var httpClient = new HttpClient();

var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);

return result.Content.Headers.ToString();
}
}

public class Test1Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await Continuations_GetSomeDataAsync();

return data;
}
}

public class Test2Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = Continuations_GetSomeDataAsync();

var data = task.GetAwaiter().GetResult();

return data;
}
}

public class Test3Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return Continuations_GetSomeDataAsync();
}
}

public class Test4Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await AsyncAwait_GetSomeDataAsync();

return data;
}
}

public class Test5Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = AsyncAwait_GetSomeDataAsync();

var data = task.GetAwaiter().GetResult();

return data;
}
}

public class Test6Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return AsyncAwait_GetSomeDataAsync();
}
}

最佳答案

您正在滥用 API。

情况是这样的:在 ASP.NET 中,一次只有一个线程可以处理一个请求。如有必要,您可以进行一些并行处理(从线程池中借用附加线程),但只有一个线程具有请求上下文(附加线程没有请求上下文)。

这是 managed by the ASP.NET SynchronizationContext .

默认情况下,当您 await Task ,该方法在捕获的 SynchronizationContext 上恢复(或捕获的 TaskScheduler ,如果没有 SynchronizationContext )。通常,这正是您想要的:异步 Controller 操作将 await一些东西,当它恢复时,它会以请求上下文恢复。

所以,这就是为什么 test5失败:

  • Test5Controller.Get执行 AsyncAwait_GetSomeDataAsync (在 ASP.NET 请求上下文中)。
  • AsyncAwait_GetSomeDataAsync执行 HttpClient.GetAsync (在 ASP.NET 请求上下文中)。
  • HTTP请求发出,HttpClient.GetAsync返回未完成的 Task .
  • AsyncAwait_GetSomeDataAsync等待 Task ;由于不完整,AsyncAwait_GetSomeDataAsync返回未完成的 Task .
  • Test5Controller.Get 当前线程直到 Task完成。
  • HTTP 响应进来,然后 Task返回者 HttpClient.GetAsync完成了。
  • AsyncAwait_GetSomeDataAsync尝试在 ASP.NET 请求上下文中恢复。但是,该上下文中已经有一个线程:线程阻塞在 Test5Controller.Get 中。 .
  • 僵局。

  • 这就是其他人工作的原因:
  • ( test1test2test3 ): Continuations_GetSomeDataAsync在 ASP.NET 请求上下文之外调度线程池的延续。这允许 Task返回者 Continuations_GetSomeDataAsync无需重新输入请求上下文即可完成。
  • ( test4test6 ):由于 Task等待,ASP.NET 请求线程不会被阻塞。这允许 AsyncAwait_GetSomeDataAsync在准备好继续时使用 ASP.NET 请求上下文。

  • 以下是最佳实践:
  • 在您的“图书馆”async方法,使用 ConfigureAwait(false)只要有可能。在你的情况下,这会改变 AsyncAwait_GetSomeDataAsync成为 var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  • 不要阻止 Task s;它是 async一直往下。换句话说,使用 await而不是 GetResult ( Task.ResultTask.Wait 也应该替换为 await )。

  • 这样,您将获得两个好处:继续( AsyncAwait_GetSomeDataAsync 方法的其余部分)在一个基本线程池线程上运行,该线程池线程不必进入 ASP.NET 请求上下文; Controller 本身是 async (它不会阻塞请求线程)。

    更多信息:
  • 我的 async / await intro post ,其中包括如何 Task 的简要说明等待者使用 SynchronizationContext .
  • Async/Await FAQ ,其中更详细地介绍了上下文。另见 Await, and UI, and deadlocks! Oh, my!即使您使用的是 ASP.NET 而不是 UI,这也适用于此处,因为 ASP.NET SynchronizationContext将请求上下文一次限制为一个线程。
  • MSDN forum post .
  • 斯蒂芬·图布 demos this deadlock (using a UI) , 和 so does Lucian Wischik .

  • 2012-07-13 更新:合并了这个答案 into a blog post .

    关于c# - HttpClient.GetAsync(...) 在使用 await/async 时永远不会返回,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10343632/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com