multithreading 由于异步,在 WebApi 中使用 HttpContext.Current 是危险的

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24956178/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 01:26:22  来源:igfitidea点击:

Using HttpContext.Current in WebApi is dangerous because of async

multithreadingasynchronousasp.net-web-apininjecthttpcontext

提问by Lukas K

My question is a bit related to this: WebApi equivalent for HttpContext.Items with Dependency Injection.

我的问题与此有点相关:WebApi 等效于 HttpContext.Items with Dependency Injection

We want to inject a class using HttpContext.Current in WebApi area using Ninject.

我们想使用 Ninject 在 WebApi 区域中使用 HttpContext.Current 注入一个类。

My concern is, this could be very dangerous, as in WebApi (everything?) is async.

我担心的是,这可能非常危险,因为在 WebApi 中(一切?)都是异步的。

Please correct me if I am wrong in these points, this is what I investigated so far:

如果我在这些方面错了,请纠正我,这是我目前调查的内容:

  1. HttpContext.Current gets the current context by Thread (I looked into the implementation directly).

  2. Using HttpContext.Current inside of async Task is not possible, because it can run on another Thread.

  3. WebApi uses IHttpController with method Task<HttpResponseMessage> ExecuteAsync=> every request is async => you cannot use HttpContext.Current inside of action method. It could even happen, more Request are executed on the same thread by coicidence.

  4. For creating controllers with injected stuff into constructors IHttpControllerActivator is used with syncmethod IHttpController Create. This is, where ninject creates Controller with all its dependencies.

  1. HttpContext.Current 通过 Thread 获取当前上下文(我直接查看了实现)。

  2. 在异步任务中使用 HttpContext.Current 是不可能的,因为它可以在另一个线程上运行。

  3. WebApi 使用 IHttpController 和方法Task<HttpResponseMessage> ExecuteAsync=> 每个请求都是异步的 => 你不能在 action 方法中使用 HttpContext.Current 。它甚至可能发生,更多的请求在同一个线程上通过 coicidence 执行。

  4. 为了创建具有注入构造函数的控制器,IHttpControllerActivator 与同步方法一起使用IHttpController Create。这就是 ninject 创建 Controller 及其所有依赖项的地方。



  • If I am correct in all of these 4 points, using of HttpContext.Current inside of an action method or any layer below is very dangerous and can have unexpected results. I saw on SO lot of accepted answers suggesting exactly this. IMHO this can work for a while, but will fail under load.

  • But when using DI to create a Controller and its dependencies, it is Ok, because this runs on one separated thread. I could get a value from the HttpContext in the constructor and it would be safe?. I wonder if each Controller is created on single thread for every request, as this could cause problem under heavy loads, where all threads from IIS could be consumed.

  • 如果我在所有这 4 点中都是正确的,那么在操作方法或下面的任何层中使用 HttpContext.Current 是非常危险的,并且可能会产生意想不到的结果。我看到很多接受的答案都暗示了这一点。恕我直言,这可以工作一段时间,但会在负载下失败。

  • 但是当使用 DI 创建 Controller 及其依赖项时,它是可以的,因为它运行在一个单独的线程上。我可以从构造函数中的 HttpContext 获取一个值,它会安全吗?. 我想知道是否每个控制器都是在单个线程上为每个请求创建的,因为这可能会在高负载下导致问题,在这种情况下,来自 IIS 的所有线程都可能被消耗。

Just to explain why I want to inject HttpContext stuff:

只是为了解释为什么我想注入 HttpContext 的东西:

  • one solution would be to get the request in controller action method and pass the needed value all the layers as param until its used somewhere deep in the code.
  • our wanted solution: all the layers between are not afected by this, and we can use the injected request somewhere deep in code (eg in some ConfigurationProvider which is dependent on URL)

    Please give me your opinion if I am totaly wrong or my suggestions are correct, as this theme seems to be very complicated. Thx in advance!

  • 一种解决方案是在控制器操作方法中获取请求,并将所需的值作为参数传递给所有层,直到它在代码深处的某个地方使用。
  • 我们想要的解决方案:之间的所有层都不受此影响,我们可以在代码深处的某个地方使用注入的请求(例如,在某些依赖于 URL 的 ConfigurationProvider 中)

    如果我完全错误或我的建议正确,请给我您的意见,因为这个主题似乎非常复杂。提前谢谢!

回答by Stephen Cleary

HttpContext.Current gets the current context by Thread (I looked into the implementation directly).

HttpContext.Current 通过 Thread 获取当前上下文(我直接查看了实现)。

It would be more correct to say that HttpContextis applied to a thread; or a thread "enters" the HttpContext.

HttpContext应用于线程会更正确;或一个线程“进入” HttpContext.

Using HttpContext.Current inside of async Task is not possible, because it can run on another Thread.

在异步任务中使用 HttpContext.Current 是不可能的,因为它可以在另一个线程上运行。

Not at all; the default behavior of async/awaitwill resume on an arbitrary thread, but that thread will enter the request context before resuming your asyncmethod.

一点也不; async/的默认行为await将在任意线程上恢复,但该线程将在恢复您的async方法之前进入请求上下文。



The key to this is the SynchronizationContext. I have an MSDN article on the subjectif you're not familiar with it. A SynchronizationContextdefines a "context" for a platform, with the common ones being UI contexts (WPF, WinPhone, WinForms, etc), the thread pool context, and the ASP.NET request context.

这其中的关键是SynchronizationContext. 如果您不熟悉,我有一篇关于该主题MSDN 文章。ASynchronizationContext为平台定义了一个“上下文”,常见的有 UI 上下文(WPF、WinPhone、WinForms 等)、线程池上下文和 ASP.NET 请求上下文。

The ASP.NET request context manages HttpContext.Currentas well as a few other things such as culture and security. The UI contexts are all tightly associated with a single thread (theUI thread), but the ASP.NET request context is not tied to a specific thread. It will, however, only allow one thread in the request context at a time.

ASP.NET 请求上下文管理HttpContext.Current以及其他一些事情,例如文化和安全性。用户界面的上下文都紧密地与一个线程相关联(UI线程),但ASP.NET请求上下文不依赖于特定的线程。但是,它一次只允许请求上下文中的一个线程。

The other part of the solution is how asyncand awaitwork. I have an asyncintro on my blog that describes their behavior. In summary, awaitby default will capture the current context (which is SynchronizationContext.Currentunless it is null), and use that context to resume the asyncmethod. So, awaitis automatically capturing the ASP.NET SynchronizationContextand will resume the asyncmethod within that request context (thus preserving culture, security, and HttpContext.Current).

该解决方案的另一部分是如何asyncawait工作。我async在我的博客上有一个介绍他们行为的介绍。总之,await默认情况下将捕获当前上下文(SynchronizationContext.Current除非它是null),并使用该上下文来恢复async方法。因此,await自动捕获 ASP.NETSynchronizationContext并将async在该请求上下文中恢复该方法(从而保留文化、安全性和HttpContext.Current)。

If you awaitConfigureAwait(false), then you're explicitly telling awaitto notcapture the context.

如果你awaitConfigureAwait(false),那么你明确告诉await捕获的上下文。

Note that ASP.NET did have to change its SynchronizationContextto work cleanly with async/await. You have to ensure that the application is compiled against .NET 4.5 and also explicitly targets 4.5 in its web.config; this is the default for new ASP.NET 4.5 projects but must be explicitly set if you upgraded an existing project from ASP.NET 4.0 or earlier.

请注意,ASP.NET 确实必须更改它SynchronizationContext才能与async/一起工作await。您必须确保应用程序是针对 .NET 4.5 编译的,并且在其 web.config 中还明确针对 4.5;这是新 ASP.NET 4.5 项目的默认设置,但如果您从 ASP.NET 4.0 或更早版本升级现有项目,则必须明确设置。

You can ensure these settings are correct by executing your application against .NET 4.5 and observing SynchronizationContext.Current. If it is AspNetSynchronizationContext, then you're good; if it's LegacyAspNetSynchronizationContext, then the settings are wrong.

您可以通过针对 .NET 4.5 执行您的应用程序并观察SynchronizationContext.Current. 如果是AspNetSynchronizationContext,那么你很好;如果是LegacyAspNetSynchronizationContext,那么设置是错误的。

As long as the settings are correct (and you are using the ASP.NET 4.5 AspNetSynchronizationContext), then you can safely use HttpContext.Currentafter an awaitwithout worrying about it.

只要设置正确(并且您使用的是 ASP.NET 4.5 AspNetSynchronizationContext),那么您就可以放心使用HttpContext.Currentawait而无需担心。

回答by Selva Ramaswamy

I am using a web api, which is using async/await methodology.

我正在使用一个 web api,它使用 async/await 方法。

also using

还使用

1) HttpContext.Current.Server.MapPath
2) System.Web.HttpContext.Current.Request.ServerVariables

This was working fine for a good amount of time which broke suddenly for no code change.

这在很长一段时间内运行良好,但由于没有代码更改而突然中断。

Spending a lot of time by reverting back to previous old versions, found the missing key causes the issue.

花了很多时间恢复到以前的旧版本,发现丢失的密钥导致了问题。

< httpRuntime targetFramework="4.5.2"  /> under system.web

I am not an expert technically. But I suggest to add the key to your web config and give it a GO.

我不是技术专家。但我建议将密钥添加到您的网络配置中并给它一个GO

回答by Lukas K

I found very good article describing exactly this problem: http://byterot.blogspot.cz/2012/04/aspnet-web-api-series-part-3-async-deep.html?m=1

我发现非常好的文章描述了这个问题:http: //byterot.blogspot.cz/2012/04/aspnet-web-api-series-part-3-async-deep.html?m=1

author investigated deeply, how the ExecuteAsync method is called in the WebApi framework and came to this conclusion:

笔者深入研究了WebApi框架中ExecuteAsync方法是如何调用的,得出了这样的结论:

ASP.NET Web API actions (and all the pipeline methods) will be called asynchronously only if you return a Task or Task<T>. This might sound obvious but none of the pipeline methods with Async suffix will run in their own threads. Using blanket Async could be a misnomer. [UPDATE: ASP.NET team indeed have confirmed that the Async is used to denote methods that return Task and can run asynchronously but do not have to]

仅当您返回 Task 或 Task<T> 时,才会异步调用 ASP.NET Web API 操作(以及所有管道方法)。这听起来可能很明显,但是带有 Async 后缀的管道方法都不会在它们自己的线程中运行。使用全面的 Async 可能用词不当。[更新:ASP.NET 团队确实已经确认 Async 用于表示返回 Task 并且可以异步运行但不必异步运行的方法]

What I understood from the article is, that the Action methods are called synchronously, but it is the caller decision.

我从文章中了解到,Action 方法是同步调用的,但它是调用者的决定。

I created a small test app for this purpose, something like this:

为此,我创建了一个小型测试应用程序,如下所示:

public class ValuesController : ApiController
{
    public object Get(string clientId, string specialValue)
    {
        HttpRequest staticContext = HttpContext.Current.Request;
        string staticUrl = staticContext.Url.ToString();

        HttpRequestMessage dynamicContext = Request;
        string dynamicUrl = dynamicContext.RequestUri.ToString();

        return new {one = staticUrl, two = dynamicUrl};
    }
}

and one Async version returning async Task<object>

和一个异步版本返回 async Task<object>

I tried to do a little DOS attack on it with jquery and could not determine any issue until I used await Task.Delay(1).ConfigureAwait(false);, which is obvious it would fail.

我试图用 jquery 对它进行一些 DOS 攻击,但在我使用之前无法确定任何问题await Task.Delay(1).ConfigureAwait(false);,这很明显它会失败。

What I took from the article is, that the problem is very complicated and Thread switch can happen when using async action method, so it is definetly NOTa good idea to use HttpContext.Current anywhere in the code called from the action methods. But as the controller is created synchronously, using HttpContext.Current in the constructor and as well in dependency injection is OK.

我从文章中得到的是,问题非常复杂,使用异步操作方法时可能会发生线程切换,因此在从操作方法调用的代码中的任何位置使用 HttpContext.Current绝对不是一个好主意。但是由于控制器是同步创建的,所以在构造函数和依赖注入中使用 HttpContext.Current 是可以的。

When somebody has another explanation to this problem please correct me as this problem is very complicated an I am still not 100% convinced.

当有人对这个问题有其他解释时,请纠正我,因为这个问题非常复杂,我仍然不是 100% 相信。

diclaimer:

免责声明:

  • I ignore for now the problem of self-hosted Web-Api withoud IIS, where HttpContext.Current would not work probably anyway. We now rely on IIS.
  • 我现在忽略了没有 IIS 的自托管 Web-Api 的问题,其中 HttpContext.Current 可能无论如何都无法工作。我们现在依赖于 IIS。