为什么在await之后HttpContext.Current为空?

105

我有以下测试WebAPI代码,我不在生产中使用WebAPI,但是因为我参与了这个问题的讨论,所以写了这个代码:WebAPI异步问题

无论如何,这是有问题的WebAPI方法:

public async Task<string> Get(int id)
{
    var x = HttpContext.Current;
    if (x == null)
    {
        // not thrown
        throw new ArgumentException("HttpContext.Current is null");
    }

    await Task.Run(() => { Task.Delay(500); id = 3; });

    x = HttpContext.Current;
    if (x == null)
    {
        // thrown
        throw new ArgumentException("HttpContext.Current is null");
    }

    return "value";
}

我曾经认为第二个异常是可以预料的,因为当await完成时,它很可能会在另一个线程上,而HttpContext.Current作为线程静态变量将不再解析为适当的值。现在,基于同步上下文,它实际上可能被强制返回到相同的线程,但在我的测试中没有进行任何花哨的操作。这只是一个简单、天真的使用await

在另一个问题的评论中,有人告诉我HttpContext.Current应该在await之后解析。甚至在这个问题的另一个评论中也有相同的说法。那么什么是真的呢?它应该解析吗?我认为不应该,但我希望能得到权威的答案,因为asyncawait还不够成熟,我找不到明确的信息。

TL;DR: 在await之后,HttpContext.Current是否可能为null


3
你的问题不够清晰 - 你已经说了你期望发生的事情,评论显示那正是正在发生的事情......所以什么让你感到困惑? - Jon Skeet
@user2674389,这是误导性的。它是AspNetSynchronizationContext来处理HttpContext,而不是await。此外,await的继续回调可能(并且很可能)在Web API执行模型的不同线程上发生。 - noseratio - open to work
不要在Web API中使用HttpContext,因为它并不支持所有的主机。有其他替代方法可以实现HttpContext所用的功能。 - Darrel Miller
1
@JoepBeusenberg 创建单独的程序集,只有在执行于特定 Web 栈的 HTTP 请求上下文中调用时才起作用,似乎会使测试、维护和重用变得更具挑战性。 - Darrel Miller
1
@DarrelMiller 恰恰相反。我已经将业务逻辑与实际的Web项目分离开来。使用依赖注入,我可以在业务逻辑之上添加一个WebAPI感知库。但是当业务逻辑在某个地方执行了.ConfigureAwait(false)时,这个库就会出现问题。由于业务层不具备Web感知能力,因此没有请求或控制器明确地传递。例如,对于一个日志记录模块,当业务逻辑编写通用的TraceInformation时,它可以注入请求详细信息,这非常有用。 - Joep Beusenberg
显示剩余4条评论
7个回答

171
请确保您正在编写一个 ASP.NET 4.5 应用程序,并且目标是 4.5。除非您运行在 4.5 上并且使用新的 "task-friendly" 同步上下文,否则 asyncawait 在 ASP.NET 上的行为未定义。
特别地,这意味着您必须执行以下操作之一:
  • httpRuntime.targetFramework 设置为 4.5
  • 在您的 appSettings 中将 aspnet:UseTaskFriendlySynchronizationContext 设置为 true
更多信息请点击此处

2
我刚刚创建了一个新的ASP.NET 4.5 WebAPI项目,复制/粘贴了你的代码,并进行了测试。对我来说它完美地工作了(没有抛出任何异常)。请重新检查您是否正在运行并针对4.5版本。 - Stephen Cleary
3
我设置了目标框架:.NET Framework 4.5。我不知道该告诉你什么,但在我的本地计算机上它肯定是空的。 - welegan
26
<httpRuntime targetFramework="4.5" /> 解决了这个问题,感谢澄清。 - welegan
1
@Vince:4.5.1 应该可以正常工作。我不确定你是否应该/可以将 targetFramework 设置为 4.5.1 或 4.5,但在 4.5.1 上使用异步应该没有问题。 - Stephen Cleary
1
如果您编写自己的托管程序处理程序会怎样?即使在Web.config中添加了这些项,我仍然会发现它们中的HttpContext.Current = null。 - Brain2000
显示剩余14条评论

33

正如@StephenCleary所正确指出的那样,您需要将此添加到您的web.config中:

<httpRuntime targetFramework="4.5" />

最初我在进行故障排除时,对于上述问题进行了全局搜索,确认它存在于我所有的网页项目中,并迅速将其排除为罪犯。最终,我想到查看这些搜索结果的完整上下文:

<!--
  For a description of web.config changes for .NET 4.5 see http://go.microsoft.com/fwlink/?LinkId=235367.

  The following attributes can be set on the <httpRuntime> tag.
    <system.Web>
      <httpRuntime targetFramework="4.5" />
    </system.Web>
-->

唉。

教训:如果你将一个Web项目升级到4.5,你仍然需要手动设置那个选项。


26
另一个需要注意的地方是这与<compilation targetFramework"4.5" />不同。 - Andrew

7

我最近遇到了这个问题。正如Stephen所指出的,不设置目标框架会导致此问题。

在我的情况下,我们的Web API已经迁移到了版本4.6.2,但是运行时目标框架从未在web config中指定,因此基本上在<system.web>标记中缺少了这个:

如果您对正在运行的框架版本有疑问,可以尝试添加以下行到任何一个Web API方法中,并设置断点以验证当前加载的类型是否为非Legacy实现:

你应该看到这个(AspNetSynchronizationContext):

enter image description here

而不是LegazyAspNetSynchronizationContext(这就是我在添加目标框架之前看到的):

enter image description here

如果您查看源代码(https://referencesource.microsoft.com/#system.web/LegacyAspNetSynchronizationContext.cs),您将看到此接口的Legacy实现缺乏异步支持。

enter image description here

我花了很多时间试图找到问题的源头,Stephen的回答帮了很大的忙。希望这个回答能够提供有关这个问题更多的信息。


3

我的测试有问题吗?还是我漏掉了一些web.config元素导致HttpContext.Current在await之后无法正确解析?

你的测试没有问题,在ASP.NET Web API中,当你使用await时,这将确保接下来的代码传递正确的HttpContext,该HttpContext在await之前是存在的,因此HttpContext.Current应该不为null。


你确定WebAPI使用同一个线程吗?我曾经处理过这种情况,其中使用了不同的线程。 - noseratio - open to work
5
ASP.NET 可以在任何线程池线程上恢复,但会使用正确的请求上下文。 - Stephen Cleary
2
是的,你说得对,线程可能不同,但HttpContext.Current在await之前和之后是相同的。我已经更新了我的问题。 - Darin Dimitrov
4
在我的代码中,使用了await后HttpContext.Current变为空值,我正在针对.net 4.6.1进行开发。 - Triynko
1
对我来说,在等待函数之前HttpContext.Current为空。 - JobaDiniz
显示剩余2条评论

1
我想评论并表示之前的答案从web.config的角度来看是正确的,然而,如果你仅使用<httpRuntime targetFramework="4.5" />,那么可以从IIS继承设置来覆盖此功能。
我的意思是: 真正的解决方法是这个设置: <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /> 如果您不明确包含此设置,而是依赖于<httpRuntime targetFramework="4.5" />来配置此设置 - 它将被IIS中的任何设置覆盖。
如果您正在调试或记录SynchronizationContext的类型,并发现它是Legacy类型,则可能需要检查IIS或托管站点级别的设置。
将产生LegacyAspNetSynchronizationContext:

Web.config:

    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="aspnet:UseLegacyEncryption" value="true" />
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.8" />
    <httpRuntime targetFramework="4.5" />
  </system.web>

enter image description here

将产生AspNetSynchronizationContext(这是您想要的):

    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
    <add key="aspnet:UseLegacyEncryption" value="true" />
    <add key="aspnet:UseLegacyMachineKeyEncryption" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.8" />
    <httpRuntime targetFramework="4.5" />
  </system.web>

*注意这里没有IIS的覆盖设置


0

我尝试了这里所有其他的答案,但是HttpContext.Current仍然为空。

.net Framework 4.6.1 / MVC

我正在使用HttpContext.Current获取上传到App_Data的映射路径

这是我解决问题的方法:

我只是将当前的HttpContext.Current存储在一个变量中,在我的await调用之后重新设置它。

var tempHttpContextCurrent = System.Web.HttpContext.Current;

var report = await reportingUtilities.GetReport(reportId, currentUserRubixId).ConfigureAwait(false);

// reset the HttpContext.Current after the await call.
System.Web.HttpContext.Current = tempHttpContextCurrent;

0
在我的情况下,问题是我在堆栈的开头忘记了await,在我的构造函数操作方法中显式地使用。
注意:使用net core 5.0和IHttpContextAccessor 所以问题出在代码中...
[HttpPost]
public async Task AnyAction()
{
    await service.MethodReturningTask(); //await was not
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接