2017-08-03 82 views
0

我有一个关于Web应用程序,使异步HTTP POST一个非常奇怪的情况...期待已久的异步任务失去HttpContext.Current

我们在TFS两个分支。我将代码从一个分支合并到另一个分支,然后发现由于System.NullReferenceException,新分支中的一些集成测试失败。我花时间确保两个分支中的代码是相同的,并且所有引用的DLL也是相同的。一切似乎都一样。

所以,我决定调试测试。

我们的测试所做的是创建一个模拟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)) 

这看起来是一个异步方法,但“客户”是我们的模仿对象所以它所做的只是支持IHttpClient接口。如果您检查线程,则在执行此行时ID不会更改。

后来,我们有:

await response.Content.ReadAsStringAsync().ConfigureAwait(true); 

现在,这条线执行时,该HttpContext.Current设置为null - 我们所有的上下文丢弃。在这个应用程序中,我们不会在任何地方调用ConfigureAwait(false),所以据我所知,我们没有理由失去上下文。

如果我改变该行:

response.Content.ReadAsStringAsync().Result; 

那么这当然是阻塞的,我们不会失去上下文。

所以两个问题:

  1. 为什么会await方法()ConfigureAwait(真)失去语境? [当然,如果人们也可以提出为什么来自一个TFS分支的同一代码在不同分支中工作时失败,但是我不期待],那么当然会获得更多的分数[
  2. 我可以看到等待.PostAsyncWithResponseMessage (url,postData)方法,因为当其他服务器处理我们的请求时,我们的线程会被阻塞。但是,检索数据后,在等待ReadAsStringAsync()...中有什么好处,换句话说,有没有很好的理由而不是使用.Result语法?

感谢

+0

https://msdn.microsoft.com/en-us/magazine/jj991977.aspx – Nkosi

+0

@Nkosi:我对这篇伟大的文章相当熟悉,并相信我们遵循所有已声明的最佳实践。请您指出我建议我阅读的这篇文章的具体部分。谢谢 – DrGriff

回答

3

如果检查线程,执行这条线时,ID不会改变。

这并不一定意味着它同步运行。大多数单元测试框架提供了一个自由线程上下文(SynchronizationContext.Currentnull),并在线程池线程上运行它们的测试。代码完全可能发生在同一个线程上恢复(特别是如果您只运行一个测试)。现在

,该行执行时,该HttpContext.Current设置为null - 我们所有的上下文丢弃。在这个应用程序中,我们不会在任何地方拨打ConfigureAwait(false),所以据我所知,我们没有理由失去语境。

我认为SynchronizationContext.Current设置为null,所以不是实际上是一个背景。可能发生的情况是,测试设置为HttpContext.Current(在某个随机线程池线程上),并且在第一次真正的异步操作之后,有时测试会在设置了Current的线程上恢复,有时不会。

在ASP.NET上,there's a request contextSynchronizationContext)处理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

+1

你的帖子真的帮了我,我发现了这个问题。原来,在两个TFS分支中,HttpContext.Current被设置为null。然而,我们使用IOC上下文(深埋在普通程序集中),发现我们的单例已被标记为[ThreadStatic]在共享公共程序集的另一个应用程序中。国际奥委会在一个分支中被设置为空,而不是其他分支,这是我们问题的原因。关于附注 - 增加ConfigureAwait(true)会在n年内告诉我们这是开发者慎重做出的决定(不应该设置为false)。谢谢! – DrGriff