编辑: This question看起来可能是同一个问题,但没有回复......
修改:在测试案例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
此处的每个端点都返回相同的数据(来自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();
}
}
答案 0 :(得分:431)
您滥用API。
在这种情况下:在ASP.NET中,一次只有一个线程可以处理请求。如有必要,您可以执行一些并行处理(从线程池中借用其他线程),但只有一个线程具有请求上下文(其他线程没有请求上下文)。
这是managed by the ASP.NET SynchronizationContext
。
默认情况下,当您await
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
):Continuations_GetSomeDataAsync
安排线程池的延续,外部 ASP.NET请求上下文。这允许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
intro post,其中包含Task
等待者如何使用SynchronizationContext
的简要说明。SynchronizationContext
将请求上下文限制为仅一次一个线程。更新2012-07-13:纳入此答案into a blog post。
答案 1 :(得分:56)
编辑:一般尽量避免做以下操作,除非为了避免死锁而进行最后的努力。阅读Stephen Cleary的第一条评论。
here快速修复。而不是写:
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(()=&gt; Library.FooAsync())。结果; }
现在将在ThreadPool上调用FooAsync,而不会有 SynchronizationContext,以及FooAsync内部使用的延续 不会被强制回到调用Sync()的线程。
答案 2 :(得分:4)
由于您使用的是.Result
或.Wait
或await
,因此最终会导致代码中出现死锁。
您可以在ConfigureAwait(false)
方法中使用async
防止死锁
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false);
您可以尽可能使用
ConfigureAwait(false)
“禁止阻止异步代码”。
答案 3 :(得分:1)
这两所学校并没有真正排除。
以下是您只需使用
的情况 Task.Run(() => AsyncOperation()).Wait();
或类似
AsyncContext.Run(AsyncOperation);
我有一个属于数据库事务属性的MVC操作。如果出现问题,这个想法(可能)会回滚在行动中完成的所有事情。这不允许上下文切换,否则事务回滚或提交本身就会失败。
我需要的库是异步的,因为它应该运行异步。
唯一的选择。将其作为普通同步调用运行。
我只是对每一个人说。
答案 4 :(得分:1)
在我的情况下,“ await”从未执行完,因为执行请求时出现异常,例如服务器没有响应,等等。用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;
}
答案 5 :(得分:0)
我在这里看:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
在这里:
看到了:
此类型及其成员供编译器使用。
考虑到await
版本有效,并且是“正确”的做事方式,您真的需要回答这个问题吗?
我的投票是:滥用API 。
答案 6 :(得分:0)
我将其放在此处的目的是为了完整起见,而不是直接与OP相关。我花了将近一天的时间调试HttpClient
请求,想知道为什么我再也回不到响应。
最后发现我忘记了在调用堆栈的更深处await
进行async
的呼叫。
感觉和分号一样好。
答案 7 :(得分:0)
我使用了很多等待,所以我没有得到响应,我转换为同步调用它开始工作
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;