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

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

编辑:在测试用例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请求上下文。
  • 以下是最佳做法:

  • 在你的“库” async方法中,尽可能使用ConfigureAwait(false) 。 在你的情况,这会改变AsyncAwait_GetSomeDataAsync var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  • 不要阻止Task ; 它一直是async的。 换句话说,使用await而不是GetResultTask.ResultTask.Wait也应该替换为await )。
  • 这样,您将获得以下两个好处:继续( AsyncAwait_GetSomeDataAsync方法的其余部分)在基本线程池线程上运行,无需输入ASP.NET请求上下文; 并且控制器本身是async (它不会阻塞请求线程)。

    更多信息:

  • 我的async / await介绍帖子,其中包括Task等待者如何使用SynchronizationContext的简要说明。
  • Async / Await常见问题解答,更详细地了解上下文。 另请参阅等待和UI以及死锁! 天啊! 即使你在ASP.NET中而不是在UI中,它也适用于这里,因为ASP.NET SynchronizationContext只限制一个线程的请求上下文。
  • 此MSDN论坛帖子。
  • Stephen Toub演示了这种僵局(使用UI),Lucian Wischik也是如此。
  • 2012-07-13更新:将此答案合并到博客文章中。


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

    从这里快速修复。 而不是写作:

    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()的线程。


    由于您正在使用.Result.Waitawait这将最终导致您的代码中的死锁

    您可以在async方法中使用ConfigureAwait(false)防止死锁

    喜欢这个:

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

    您可以尽可能使用ConfigureAwait(false)来阻止异步代码。

    链接地址: http://www.djcxy.com/p/82675.html

    上一篇: HttpClient.GetAsync(...) never returns when using await/async

    下一篇: Google API and .NET Compact Framework