编辑:此问题看起来可能是同一个问题,但没有响应。。。
编辑:在测试用例5中,任务似乎被困在waitingforactivation
状态中。
我在使用.NET4.5中的System.Net.HTTP.HttpClient时遇到了一些奇怪的行为--“等待”调用(例如)的结果HttpClient.GetAsync(。。。)
将永远不会返回。
只有在使用新的Async/Await语言功能和Tasks API的特定情况下,才会出现这种情况--当只使用continuations时,代码似乎总是能正常工作。
下面是一些再现问题的代码--将其放到Visual Studio 11中的一个新的“MVC4 WebApi项目”中,以公开以下GETendpoint:
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
这里的每个endpoint都返回相同的数据(来自stackoverflow.com的响应头),除了/api/test5
,它永远不会完成。
要复制的代码:
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.NETSynchronizationContext
管理的。
默认情况下,当等待
一个任务
时,该方法将在捕获的SynchronizationContext
(或者捕获的TaskScheduler
(如果没有SynchronizationContext
))上恢复。通常,这正是您想要的:一个异步控制器动作将等待
某个东西,当它恢复时,它将用请求上下文恢复。
下面是test5
失败的原因:
Test5Controller.get
执行AsyncaWait_GetSomeDataAsync
(在ASP.NET请求上下文中)。AsyncaWait_GetSomeDataAsync
执行HttpClient.GetAsync
(在ASP.NET请求上下文中)。HttpClient.GetAsync
返回未完成的任务
。AsyncaWait_GetSomeDataAsync
等待任务
;由于未完成,ASYNCAWAIT_GetSomeDataAsync
返回未完成的任务
。Test5Controller.Get
将阻塞当前线程,直到该任务
完成。HttpClient.GetAsync
返回的任务
完成。ASYNCAWAIT_GetSomeDataAsync
尝试在ASP.NET请求上下文中恢复。但是,上下文中已经有一个线程:test5controller.get
.以下是其他方法起作用的原因:
test1
,test2
和test3
):continuations_GetSomeDataAsync
在ASP.NET请求上下文之外将继续调度到线程池。这允许Continuations_GetSomeDataAsync
返回的任务
完成,而不必重新输入请求上下文。test4
和test6
):由于等待任务
,因此ASP.NET请求线程不会被阻止。这允许AsyncAWAIT_GetSomeDataAsync
在准备继续时使用ASP.NET请求上下文。以下是最佳实践:
async
方法中,尽可能使用configureAwait(false)
。在您的示例中,这会将AsyncaWAIT_GetSomeDataAsync
更改为var result=await httpClient.GetAsync(“http://stackoverflow.com”,httpCompletionOption.ResponseHeadersRead).configureAwait(false);
任务
;它一直是异步
。换句话说,使用Await
而不是GetResult
(Task.Result
和Task.Wait
也应替换为Await
)。这样,您就获得了两个好处:继续(AsyncAWAIT_GetSomeDataAsync
方法的剩余部分)在基本线程池线程上运行,该线程不必进入ASP.NET请求上下文;控制器本身是async
(它不阻塞请求线程)。
更多信息:
异步
/等待
介绍文章,其中包括任务
等待者如何使用synchronizationContext
的简要说明。synchronizationContext
将请求上下文限制为一次仅有一个线程。更新2012-07-13:将此答案合并到一篇博客文章中。
编辑:一般来说,尽量避免做下面的事情,除非是为了避免死锁而做的最后努力。阅读斯蒂芬·克利里的第一篇评论。
从这里快速修复。而不是写:
Task tsk = AsyncOperation();
tsk.Wait();
尝试:
Task.Run(() => AsyncOperation()).Wait();
或者如果您需要一个结果:
var result = Task.Run(() => AsyncOperation()).Result;
来自来源(经过编辑以匹配上面的示例):
现在将在线程池上调用AsyncOperation,在线程池中不会有SynchronizationContext,并且AsyncOperation内部使用的延续不会被强制返回到调用线程。
对我来说,这看起来是一个有用的选项,因为我没有让它一路异步的选项(我更喜欢这样)。
从来源来看:
确保FooAsync方法中的await找不到要封送回的上下文。最简单的方法是从线程池调用异步工作,例如通过将调用包装在task.run中,例如。
int Sync(){return task.run(()=>Library.FooAsync())。result;}
现在将在线程池中调用FooAsync,在线程池中不会有SynchronizationContext,并且FooAsync内部使用的延续不会被强制返回到调用Sync()的线程。
由于您使用的是.result
或.wait
或await
,这将最终导致代码中的死锁。
您可以在async
方法中使用configureAwait(false)
来防止死锁
像这样:
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false);
您可以尽可能使用configureAwait(false)
来实现Don't Block异步代码。