Ben*_*Fox 297 .net c# asynchronous async-await dotnet-httpclient
编辑: 这个问题看起来可能是同一个问题,但没有回复......
编辑:在测试用例5中,任务似乎陷入WaitingForActivation
状态.
我在.NET 4.5中使用System.Net.Http.HttpClient遇到了一些奇怪的行为 - 其中"等待"调用(例如)的结果httpClient.GetAsync(...)
将永远不会返回.
只有在使用新的async/await语言功能和Tasks API时才会出现这种情况 - 在仅使用continuation时,代码似乎始终有效.
下面是一些重现问题的代码 - 将其放入Visual Studio 11中新的"MVC 4 WebApi项目"中,以显示以下GET端点:
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
Run Code Online (Sandbox Code Playgroud)
这里的每个端点都返回相同的数据(来自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();
}
}
Run Code Online (Sandbox Code Playgroud)
Ste*_*ary 450
您滥用API.
情况就是这样:在ASP.NET中,一次只有一个线程可以处理请求.如有必要,您可以执行一些并行处理(从线程池中借用其他线程),但只有一个线程具有请求上下文(其他线程没有请求上下文).
这是由ASP.NET管理的SynchronizationContext
.
默认情况下,当您执行await
a时Task
,该方法将在捕获的SynchronizationContext
(或捕获的TaskScheduler
,如果没有SynchronizationContext
)上恢复.通常情况下,这正是您想要的:异步控制器操作将会出现问题await
,当它恢复时,它会随请求上下文一起恢复.
所以,这就是test5
失败的原因:
Test5Controller.Get
执行AsyncAwait_GetSomeDataAsync
(在ASP.NET请求上下文中).AsyncAwait_GetSomeDataAsync
执行HttpClient.GetAsync
(在ASP.NET请求上下文中).HttpClient.GetAsync
返回未完成的Task
.AsyncAwait_GetSomeDataAsync
等待Task
; 由于它不完整,AsyncAwait_GetSomeDataAsync
返回未完成Task
.Test5Controller.Get
阻止当前线程直到Task
完成.Task
返回的HttpClient.GetAsync
是完成的.AsyncAwait_GetSomeDataAsync
尝试在ASP.NET请求上下文中恢复.但是,在该上下文中已经存在一个线程:阻塞的线程Test5Controller.Get
.这就是其他人工作的原因:
test1
,test2
和test3
):在ASP.NET请求上下文之外Continuations_GetSomeDataAsync
调度线程池的延续.这允许返回者完成而无需重新输入请求上下文.Task
Continuations_GetSomeDataAsync
test4
和test6
):自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
替代GetResult
(Task.Result
和Task.Wait
也应更换await
).这样,您可以获得两个好处:继续(AsyncAwait_GetSomeDataAsync
方法的其余部分)在基本线程池线程上运行,该线程不必进入ASP.NET请求上下文; 和控制器本身async
(它不会阻止请求线程).
更多信息:
async
/ await
简介帖子,其中包括对Task
等待者如何使用的简要描述SynchronizationContext
.SynchronizationContext
将请求上下文限制为仅一个线程.更新2012-07-13:将此答案纳入博客文章.
Yko*_*kok 58
编辑:一般尽量避免做以下操作,除非作为最后的努力,以避免死锁.阅读Stephen Cleary的第一条评论.
从这里快速修复.而不是写:
Task tsk = AsyncOperation();
tsk.Wait();
Run Code Online (Sandbox Code Playgroud)
尝试:
Task.Run(() => AsyncOperation()).Wait();
Run Code Online (Sandbox Code Playgroud)
或者如果您需要结果:
var result = Task.Run(() => AsyncOperation()).Result;
Run Code Online (Sandbox Code Playgroud)
从源(编辑以匹配上面的例子):
现在将在ThreadPool上调用AsyncOperation,其中不存在SynchronizationContext,并且AsyncOperation内部使用的continuation将不会强制返回到调用线程.
对我来说,这看起来像一个可用的选项,因为我没有选择让它一直异步(我更喜欢).
从来源:
确保FooAsync方法中的await没有找到要编组的上下文.最简单的方法是从ThreadPool调用异步工作,例如将调用包装在Task.Run中,例如
int Sync(){return Task.Run(()=> Library.FooAsync()).结果; }
现在将在ThreadPool上调用FooAsync,其中不存在SynchronizationContext,并且FooAsync内部使用的延续不会被强制回到调用Sync()的线程.
由于您正在使用.Result
或,.Wait
否则await
最终将导致代码死锁。
您可以ConfigureAwait(false)
在防止死锁的async
方法中使用
像这样:
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)
您可以
ConfigureAwait(false)
在任何情况下使用“请勿阻止异步代码”。
归档时间: |
|
查看次数: |
169467 次 |
最近记录: |