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

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

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

这只发生在使用新的async/await语言功能和任务API时的特定情况下-当只使用延续时,代码似乎总是工作。

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

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

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

我是否在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管理的。净SynchronizationContext。

默认情况下,当您等待一个任务时,该方法将在一个捕获的SynchronizationContext(或者一个捕获的TaskScheduler,如果没有SynchronizationContext)上恢复。通常,这正是您想要的:异步控制器操作将等待某些内容,当它恢复时,它将与请求上下文一起恢复。

下面是test5失败的原因:

Test5Controller.Get executes AsyncAwait_GetSomeDataAsync (within the ASP.NET request context). AsyncAwait_GetSomeDataAsync executes HttpClient.GetAsync (within the ASP.NET request context). The HTTP request is sent out, and HttpClient.GetAsync returns an uncompleted Task. AsyncAwait_GetSomeDataAsync awaits the Task; since it is not complete, AsyncAwait_GetSomeDataAsync returns an uncompleted Task. Test5Controller.Get blocks the current thread until that Task completes. The HTTP response comes in, and the Task returned by HttpClient.GetAsync is completed. AsyncAwait_GetSomeDataAsync attempts to resume within the ASP.NET request context. However, there is already a thread in that context: the thread blocked in Test5Controller.Get. Deadlock.

以下是其他方法有效的原因:

(test1, test2,和test3): Continuations_GetSomeDataAsync调度线程池的延续,在ASP。NET请求上下文。这允许由Continuations_GetSomeDataAsync返回的任务完成,而不必重新进入请求上下文。 (test4和test6):由于任务正在等待,ASP。NET请求线程没有阻塞。这允许AsyncAwait_GetSomeDataAsync使用ASP。当它准备继续时,NET请求上下文。

以下是最佳实践:

在你的“库”异步方法中,尽可能使用ConfigureAwait(false)。在你的情况下,这将改变AsyncAwait_GetSomeDataAsync为var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); 不要阻塞任务;它完全是异步的。换句话说,使用await而不是GetResult (Task。结果和任务。Wait也应该替换为await)。

这样,你可以得到两个好处:延续(AsyncAwait_GetSomeDataAsync方法的剩余部分)在基本线程池线程上运行,不需要进入ASP。NET请求上下文;而且控制器本身是异步的(它不会阻塞请求线程)。

更多信息:

我的async/await介绍帖子,其中包括任务等待器如何使用SynchronizationContext的简要描述。 Async/Await常见问题解答,其中详细介绍了上下文。还可以查看Await、UI和死锁!哦,我的天!这在这里也适用,即使你在ASP。NET而不是UI,因为ASP。NET SynchronizationContext将请求上下文限制为一次只有一个线程。 这是MSDN论坛的帖子。 Stephen Toub演示了这种死锁(使用一个UI), Lucian Wischik也演示了。

更新2012-07-13:将这个答案整合到一篇博客文章中。


编辑:一般来说,尽量避免做下面的事情,除非是为了避免死锁而做的最后努力。阅读Stephen Cleary的第一条评论。

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

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

Try:

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

或者如果你需要一个结果:

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

从源代码(经过编辑以匹配上面的示例):

AsyncOperation现在将在线程池上调用 将不会是一个SynchronizationContext,以及内部使用的延续 AsyncOperation不会被强制返回到调用线程。

对我来说,这看起来是一个有用的选项,因为我没有选择使它异步的所有方式(这是我更喜欢的)。

来源:

Ensure that the await in the FooAsync method doesn’t find a context to marshal back to. The simplest way to do that is to invoke the asynchronous work from the ThreadPool, such as by wrapping the invocation in a Task.Run, e.g. int Sync() { return Task.Run(() => Library.FooAsync()).Result; } FooAsync will now be invoked on the ThreadPool, where there won’t be a SynchronizationContext, and the continuations used inside of FooAsync won’t be forced back to the thread that’s invoking Sync().


这两个学派并不是真正的排斥。

这里是您必须使用的场景

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

或者类似的东西

   AsyncContext.Run(AsyncOperation);

我有一个MVC操作,是在数据库事务属性下。这个想法(可能)是在发生错误时回滚操作中所做的所有事情。这不允许上下文切换,否则事务回滚或提交本身就会失败。

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

这是唯一的选择。运行它作为一个正常的同步调用。

我只是说,各有千秋。


由于您使用的是. result或. wait或await,这将导致代码死锁。

你可以在异步方法中使用ConfigureAwait(false)来防止死锁

是这样的:

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

你可以在任何可能的地方使用ConfigureAwait(false)来不阻塞异步代码。


我把它放在这里更多的是为了完整,而不是与op直接相关。我花了将近一天的时间调试HttpClient请求,想知道为什么我从来没有得到回应。

最后发现我已经忘记等待异步调用进一步向下的调用堆栈。

感觉就像少了一个分号。


在我的情况下,'等待'从来没有完成,因为在执行请求时异常,例如服务器不响应等。用try. catch包围它来确定发生了什么,它也会优雅地完成你的“等待”。

public async Task<Stuff> GetStuff(string id)
{
    string path = $"/api/v2/stuff/{id}";
    try
    {
        HttpResponseMessage response = await client.GetAsync(path);
        if (response.StatusCode == HttpStatusCode.OK)
        {
            string json = await response.Content.ReadAsStringAsync();
            return JsonUtility.FromJson<Stuff>(json);
        }
        else
        {
            Debug.LogError($"Could not retrieve stuff {id}");
        }
    }
    catch (Exception exception)
    {
        Debug.LogError($"Exception when retrieving stuff {exception}");
    }
    return null;
}

我使用了很多等待,所以我没有得到响应,我转换为同步调用它开始工作

            using (var client = new HttpClient())
            using (var request = new HttpRequestMessage())
            {
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                request.Method = HttpMethod.Get;
                request.RequestUri = new Uri(URL);
                var response = client.GetAsync(URL).Result;
                response.EnsureSuccessStatusCode();
                string responseBody = response.Content.ReadAsStringAsync().Result;