关于创建异步Http Post的Web应用程序,我有一个非常奇怪的情况......
我们在TFS有两个分支。我将代码从一个分支合并到另一个分支,然后发现由于System.NullReferenceException,新分支中的某些集成测试失败。我花时间确保两个分支中的代码相同,并且所有引用的DLL也是相同的。一切似乎都一样。
所以,我决定调试测试。
我们的测试是创建Mock IHttpClient对象。我们以这样一种方式存根Mock对象:clientMock.PostAsyncWithResponseMessage(x,y)返回一个新的HttpResponseMessage()对象(我们在其上设置了各种属性)。
所以,代码看起来像这样:
using (var response = await client.PostAsyncWithResponseMessage(url, postData).ConfigureAwait(true))
{
if (response.IsSuccessStatusCode)
{
ret.Response = await response.Content.ReadAsStringAsync().ConfigureAwait(true);
ret.ContentType = response.Content.Headers.ContentType.ToString();
}
ret.StatusCode = response.StatusCode.ToInt();
}
一次一行:
await client.PostAsyncWithResponseMessage(url, postData).ConfigureAwait(true))
这看起来像是一个异步方法,但“client”是我们的Mock对象,所以它所做的就是支持IHttpClient接口。如果检查线程,则执行此行时ID不会更改。
稍后,我们有:
await response.Content.ReadAsStringAsync().ConfigureAwait(true);
现在,当这一行执行时,HttpContext.Current被设置为null - 我们的所有上下文都被删除了。在这个应用程序中,我们不会在任何地方调用ConfigureAwait(false),所以据我所知,我们没有理由失去上下文。
如果我将该行更改为:
response.Content.ReadAsStringAsync().Result;
然后这当然是阻止,我们不会失去上下文。
所以有两个问题:
感谢
答案 0 :(得分:4)
如果检查线程,执行此行时ID不会改变。
这并不一定意味着它同步运行。大多数单元测试框架提供了一个自由线程上下文(SynchronizationContext.Current
是null
)并在线程池线程上运行它们的测试。完全有可能代码恰好在同一个线程上恢复(特别是如果你只运行一个测试)。
现在,当此行执行时,
HttpContext.Current
设置为null
- 我们的所有上下文都被删除了。在这个应用程序中,我们不会在任何地方调用ConfigureAwait(false)
,所以据我所知,我们没有理由失去上下文。
我认为SynchronizationContext.Current
设置为null
,因此实际上不是上下文。可能发生的是测试是设置HttpContext.Current
(在一些随机线程池线程上),并且在第一次真正异步操作之后,有时测试在设置Current
的线程上恢复,有时它不会“T
在ASP.NET上,there's a request context(SynchronizationContext
)处理HttpContext.Current
的传播。如果您的单元测试中没有类似的东西,那么就没有保留HttpContext.Current
的上下文。
如果您正在寻找快速解决方案,那么您可以使用我的AsyncContext
type。这将提供一个单线程上下文,以便所有异步代码将在同一个线程上恢复 - 与ASP.NET的上下文不同,但类似于足以对某些测试有用:
void TestMethod()
{
AsyncContext.Run(async () =>
{
// Test method body goes here.
});
}
另一方面,ConfigureAwait(true)
只是噪音; true
是默认行为。
是否有充分的理由不使用
.Result
语法?
从您的代码中不清楚服务器的响应是在PostAsyncWithResponseMessage
完成的时间内实际读取的。根据您的问题描述(以及Result
在您的测试中修复它的事实),听起来ReadAsStringAsync
实际上是异步行为。
如果是这种情况,那么有两个很好的理由不阻止:1)你不必要地阻止一个线程,2)你自己打开了deadlock。