等待异步任务丢失HttpContext.Current

时间:2017-08-03 15:40:21

标签: asynchronous async-await rhino-mocks

关于创建异步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;

然后这当然是阻止,我们不会失去上下文。

所以有两个问题:

  1. 为什么await方法()。ConfigureAwait(true)会丢失上下文? [如果有人也可以建议为什么来自一个TFS分支的相同代码在另一个分支机构工作时失败,那么当然会给予更多分数,但我并不期望这样做]
  2. 我可以看到等待.PostAsyncWithResponseMessage(url,postData)方法的明显好处,因为否则我们的线程会在其他服务器处理我们的请求时被阻止。但是,在检索数据之后,等待ReadAsStringAsync()的优势是什么......换句话说,是否有充分的理由来使用.Result语法?
  3. 感谢

1 个答案:

答案 0 :(得分:4)

  

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

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

  

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

我认为SynchronizationContext.Current设置为null,因此实际上不是上下文。可能发生的是测试是设置HttpContext.Current(在一些随机线程池线程上),并且在第一次真正异步操作之后,有时测试在设置Current的线程上恢复,有时它不会“T

在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