c# httpclient同步




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

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

编辑:在测试用例5中,任务看起来停留在WaitingForActivation状态。

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

这仅在使用新的异步/等待语言功能和任务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的响应头文件)。

我是否遇到过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中,一次只能有一个线程处理请求。 如有必要,您可以执行一些并行处理(从线程池中借用其他线程),但只有一个线程拥有请求上下文(其他线程没有请求上下文)。

由ASP.NET SynchronizationContext管理

默认情况下,当您await Task ,该方法会在捕获的SynchronizationContext (或捕获的TaskScheduler ,如果没有SynchronizationContext )上恢复。 通常情况下,这正是您想要的:异步控制器操作将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响应进入,并且由HttpClient.GetAsync返回的Task完成。
  • AsyncAwait_GetSomeDataAsync尝试在ASP.NET请求上下文中恢复。 但是,在该上下文中已经有一个线程: Test5Controller.Get被阻塞的线程。
  • 僵局。

以下是其他原因的原因:

  • test1test2test3 ): Continuations_GetSomeDataAsync在ASP.NET请求上下文之外调度线程池的延续。 这允许Continuations_GetSomeDataAsync返回的Task完成,而无需重新输入请求上下文。
  • test4test6 ):由于Task正在等待 ,ASP.NET请求线程不会被阻止。 这允许AsyncAwait_GetSomeDataAsync在准备好继续时使用ASP.NET请求上下文。

以下是最佳做法:

  1. 在你的“库” async方法中,尽可能使用ConfigureAwait(false) 。 在你的情况,这会改变AsyncAwait_GetSomeDataAsync var result = await httpClient.GetAsync("http://.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  2. 不要阻止Task ; 它一直是async的。 换句话说,使用await而不是GetResultTask.ResultTask.Wait也应该替换为await )。

这样,您将获得以下两个好处:继续( AsyncAwait_GetSomeDataAsync方法的其余部分)在基本线程池线程上运行,无需输入ASP.NET请求上下文; 并且控制器本身是async (它不会阻塞请求线程)。

更多信息:

2012-07-13更新:将此答案合并到博客文章中



编辑:通常尽量避免做下面的事情,除非作为最后的努力避免死锁。 阅读Stephen Cleary的第一条评论。

here快速修复。 而不是写作:

Task tsk = AsyncOperation();
tsk.Wait();

尝试:

Task.Run(() => AsyncOperation()).Wait();

或者如果您需要结果:

var result = Task.Run(() => AsyncOperation()).Result;

来源(编辑以匹配上面的例子):

现在将在ThreadPool上调用AsyncOperation,其中不会有SynchronizationContext,并且在AsyncOperation内部使用的延续不会被强制回到调用线程。

对我来说,这看起来像一个可用的选项,因为我没有选择使其异步(我更喜欢)。

来源:

确保FooAsync方法中的await没有找到上送回的上下文。 最简单的方法是从ThreadPool调用异步工作,比如通过将调用包装在Task.Run中,例如

int Sync(){return Task.Run(()=> Library.FooAsync())。 }

现在将在ThreadPool上调用FooAsync,其中不会有SynchronizationContext,并且在FooAsync中使用的延续不会被强制回到调用Sync()的线程。


这两所学校并不是真的排除在外。

以下是您只需使用的场景

   Task.Run(() => AsyncOperation()).Wait(); 

或类似的东西

   AsyncContext.Run(AsyncOperation);

我有一个在数据库事务属性下的MVC动作。 这个想法是(可能)回滚在动作中做的所有事情。 这不允许上下文切换,否则事务回滚或提交将自行失败。

我需要的库是异步的,因为它需要运行异步。

唯一的选择。 将其作为普通同步呼叫运行。

我只是对每一个人说。





dotnet-httpclient