为什么等待后 HttpContext. 当前为空?

我有以下的测试 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应该在等待之后解决。对于这个问题,甚至还有另外一个评论指出了同样的问题。那么什么是真的?应该解决吗?我认为没有,但我想要一个权威的答案,因为 asyncawait是新的,我不能找到任何权威的东西。

await之后 HttpContext.Current是否可能是 null

51174 次浏览

Is my test flawed, or is there some web.config element I'm missing here that would make HttpContext.Current resolve correctly after an await?

Your test is not flawed and HttpContext.Current should not be null after the await because in ASP.NET Web API when you await, this will ensure that the code that follows this await is passed the correct HttpContext that was present before the await.

Please ensure you are writing an ASP.NET 4.5 application, and targeting 4.5. async and await have undefined behavior on ASP.NET unless you are running on 4.5 and are using the new "task-friendly" synchronization context.

In particular, this means you must either:

  • Set httpRuntime.targetFramework to 4.5, or
  • In your appSettings, set aspnet:UseTaskFriendlySynchronizationContext to true.

More information is available here.

As @StephenCleary correctly pointed out, you need this in your web.config:

<httpRuntime targetFramework="4.5" />

When I was first troubleshooting this, I did a solution-wide search for the above, confirmed it was present in all my web projects and quickly dismissed it as the culprit. Eventually it occurred to me to look at those search results in full context:

<!--
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>
-->

Doh.

Lesson: If you upgrade a web project to 4.5, you still need to get that setting in place manually.

I ran into this issue recently. As Stephen pointed out not setting explicitly the target framework can generate this issue.

In my case, our Web API was migrated to version 4.6.2 but the runtime target framework was never specified in the web config, so basically this was missing inside the <system.web> tag:

If you have doubts about the framework version you are running this may help: Add the following line on any of your Web API methods and set a breakpoint to verify what type is currently loaded at runtime and verify it is not a Legacy implementation:

You should see this (AspNetSynchronizationContext):

enter image description here

Instead of LegazyAspNetSynchronizationContext (Which was what I saw before adding the target framework):

enter image description here

If you go to the source code (https://referencesource.microsoft.com/#system.web/LegacyAspNetSynchronizationContext.cs) you will see that the Legacy implementation of this interface lacks of asynchronous support.

enter image description here

I spent a lot of time trying to find the source of the issue and Stephen´s response helped a lot. Hope this answer provides some more information about the issue.

I wanted to comment and say that the previous answers hit the nail on the head from a web.config perspective, however, there can be inherited settings from IIS that can override this functionality if you simply use <httpRuntime targetFramework="4.5" />.

What I mean: The true fix here is this setting: <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />

If you don't explicitly include this setting but rely on <httpRuntime targetFramework="4.5" /> to configure this setting - it will be overridden by any settings in IIS.

If you are debugging or logging the type of SynchronizationContext and you find out it's of type Legacy, you may want to check for a setting at the IIS or hosting Site level.

Will yield 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

Will yield AspNetSynchronizationContext (this is what you want):

    <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>

*Note there is no override setting in IIS here

I tried all the other answers here and HttpContext.Current was still null.

.net Framework 4.6.1 / MVC

I'm using HttpContext.Current to get the mapped path for an upload to App_Data

This is how I fixed the issue:

I just obtain the current HttpContext.Current in a variable and then reset it after my await calls.

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;

In my case the problem was I forget await in the begining of stack, explicity in my constructor action method

NOTE: Using net core 5.0 with IHttpContextAccessor

So the problem was in code...

[HttpPost]
public async Task AnyAction()
{
await service.MethodReturningTask(); //await was not
}