Edit: This question看起来可能是同样的问题,但没有回复......
Edit: 在测试用例5中,任务似乎停留在 WaitingForActivation
状态 .
我在.NET 4.5中使用System.Net.Http.HttpClient遇到了一些奇怪的行为 - 其中"awaiting"调用(例如) httpClient.GetAsync(...)
的结果将永远不会返回 .
这仅在使用新的异步/等待语言功能和任务API的某些情况下发生 - 当仅使用延续时,代码似乎始终有效 .
下面是一些重现问题的代码 - 将其放入Visual Studio 11中新的“MVC 4 WebApi项目”中,以显示以下GET endpoints :
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
这里的每个 endpoints 都返回相同的数据(来自stackoverflow.com的响应头),但 /api/test5
永远不会完成 .
我在HttpClient类中遇到过错误,还是以某种方式滥用了API?
Code to reproduce:
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();
}
}
5 回答
您滥用API .
情况就是这样:在ASP.NET中,一次只有一个线程可以处理请求 . 如有必要,您可以执行一些并行处理(从线程池中借用其他线程),但只有一个线程具有请求上下文(其他线程没有请求上下文) .
这是managed by the ASP.NET SynchronizationContext .
默认情况下,当您
await
Task
时,该方法将在捕获的SynchronizationContext
(或捕获的TaskScheduler
,如果没有SynchronizationContext
)上恢复 . 通常情况下,这正是您想要的:异步控制器操作将会发生一些事情,当它恢复时,它将继续执行请求上下文 .所以,这就是
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
blocks 当前线程直到Task
完成 .HTTP响应进入,
HttpClient.GetAsync
返回的Task
已完成 .AsyncAwait_GetSomeDataAsync
尝试在ASP.NET请求上下文中恢复 . 但是,在该上下文中已经存在一个线程:在Test5Controller.Get
中阻塞的线程 .死锁 .
这就是其他人工作的原因:
(
test1
,test2
和test3
):Continuations_GetSomeDataAsync
在ASP.NET请求上下文之外调度线程池的延续 . 这允许Continuations_GetSomeDataAsync
返回的Task
完成而无需重新输入请求上下文 .(
test4
和test6
):由于等待Task
,因此不会阻止ASP.NET请求线程 . 这允许AsyncAwait_GetSomeDataAsync
在准备好继续时使用ASP.NET请求上下文 .这是最好的做法:
在"library"
async
方法中,尽可能使用ConfigureAwait(false)
. 在你的情况下,这将改变AsyncAwait_GetSomeDataAsync
为var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
不要阻止
Task
;它一直都是async
. 换句话说,使用await
而不是GetResult
(Task.Result
和Task.Wait
也应该用await
替换) .这样,您可以获得两个好处:继续(
AsyncAwait_GetSomeDataAsync
方法的其余部分)在基本线程池线程上运行,该线程不必进入ASP.NET请求上下文;并且控制器本身是async
(不阻止请求线程) .更多信息:
我的async/await intro post,其中包括
Task
awaiters如何使用SynchronizationContext
的简要说明 .Async/Await FAQ,详细介绍了上下文 . 另请参阅Await, and UI, and deadlocks! Oh, my!,即使您使用的是ASP.NET而不是UI,它也适用于此,因为ASP.NET
SynchronizationContext
一次将请求上下文限制为仅一个线程 .这MSDN forum post .
Stephen Toub demos this deadlock (using a UI)和so does Lucian Wischik .
Update 2012-07-13: 收纳了这个答案into a blog post .
由于您使用的是
.Result
或.Wait
或await
,因此最终会导致代码中出现 deadlock .你可以在
async
方法中使用ConfigureAwait(false)
来实现 preventing deadlock像这样:
这两所学校并没有真正排除 .
这是您只需使用的场景
或类似的东西
我有一个在数据库事务属性下的MVC操作 . 如果出现问题,这个想法(可能)会回滚在行动中完成的所有事情 . 这不允许上下文切换,否则事务回滚或提交将自行失败 .
我需要的库是异步的,因为它预计会运行异步 .
唯一的选择 . 将其作为普通同步调用运行 .
我只是对每一个人说 .
我在这看:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
和这里:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx
并看到:
考虑
await
版本有效,并且是'right'做事的方式,你真的需要这个问题的答案吗?我的投票是: Misusing the API .
编辑:一般尽量避免做以下操作,除非作为最后的努力避免死锁 . 阅读Stephen Cleary的第一条评论 .
从here快速修复 . 而不是写:
尝试:
或者如果您需要结果:
从源代码(编辑以匹配上面的例子):
对我来说,这看起来像一个可用的选项,因为我没有选择让它一直异步(我更喜欢) .
从来源: