Web API 服务 - 如何在异步任务中使用“HttpContext.Current”

Posted

技术标签:

【中文标题】Web API 服务 - 如何在异步任务中使用“HttpContext.Current”【英文标题】:Web API Service - How to use "HttpContext.Current" inside async task 【发布时间】:2014-05-21 17:40:33 【问题描述】:

我正在使用 webApi 休息服务的“发布”异步方法:

public async Task<object> Post([FromBody]string data)

      object response = ExecuteServerLogics(data);

      return response;

上述代码运行良好,但在某些客户端调用中,我们遇到了性能问题。

在这里阅读了一些文章后,我注意到我们的 webApi 休息服务, 并没有真正与传入的 Web 请求异步工作, 因为我们忘了使用 async/await 模式

public async Task<object> Post([FromBody]string data)

      object response = await Task<object>.Run( () =>
      
           return ExecuteServerLogics(data);
      );

      return response;

在这个修复之后,我们注意到性能变得更好了, 但我们发现了另一个批评者问题: 当访问 HttpContext.Current - 它返回 Null 引用:

public async Task<object> Post([FromBody]string data)

      object response = await Task<object>.Run( () =>
      
           var currentContext = HttpContext.Current; // Returns Null!
           return ExecuteServerLogics(data);
      );

      return response;

我们试图找到解决方案,在大多数帖子中我们发现我们应该通过 工作线程的 HttpContext 引用到执行服务器逻辑的内部任务。 这个解决方案的问题是服务器的逻辑方法,使用许多静态类 “HttpContext.Current”如-

    记录器调用。 检索 user.identity 的静态安全类 检索传入请求的会话数据等的静态安全类。

因此,传递工作线程的 "HttpContext.Current" 引用并不能解决问题。

当我们尝试下一个解决方案时:

public async Task<object> Post([FromBody]string data)
    
          // Save worker context:
          var currentContext = HttpContext.Current; 

          object response = await Task<object>.Run( () =>
          
               // Set the context of the current task :
               HttpContext.Current = currentContext ; // Causes the calls not to work asynchronously for some reason!

               // Executes logics for current request:
               return ExecuteServerLogics(data);
          );

          return response;
    

由于某种原因,我们注意到性能再次变差,就像它又恢复了同步工作一样。

我们的问题是:

1. 为什么在上一个例子中,在 await 任务中设置 "HttpContext.Current", 导致请求返回与同步结果相同的不良性能结果?

2. 有没有另一种方法可以在调用的内部任务中使用 "HttpContext.Current" - "ExecuteServerLogics", 并且在所有也调用 "HttpContext.Current" 的静态类中? 我在某种程度上做错了整个设计吗?

谢谢!

【问题讨论】:

【参考方案1】:

从头开始:

public async Task<object> Post([FromBody]string data)

  object response = ExecuteServerLogics(data);
  return response;

不要忽略编译器警告;编译器会为此方法生成一个警告,明确指出它将同步运行。

继续:

在一些客户的电话中,我们遇到了性能问题。

服务器上的异步代码不会对于单独的单个调用更快。它只会帮助您扩展您的服务器。

特别是,Task.Run 将抵消async 的所有性能优势,然后进一步降低性能。我相信您测量的性能改进是巧合。

在大多数帖子中,我们发现我们应该将工作线程的 HttpContext 引用传递给执行服务器逻辑的内部任务。

那些帖子是错误的。恕我直言。您最终会使用后台线程中的 HttpContext 对象,而该对象专门设计为只能从请求线程访问。

我是否在某种程度上做错了整个设计?

我建议您退后一步,从大局考虑。当一个请求进来时,它有一定的工作要做。该工作是同步完成还是异步完成对客户来说并不重要;这两种方法所花费的时间大致相同。

如果您需要尽早返回给客户端,那么您将需要一个完全不同的架构。通常的做法是将工作排入可靠队列(例如 Azure 队列),拥有单独的后端(例如 Azure WebRole),并在工作完成时主动通知客户端(例如 SignalR)。

这并不是说async 没用。如果ExecuteServerLogics是一个I/O绑定的方法,那么它应该是异步的而不是阻塞的,然后你可以像这样使用异步方法:

public async Task<object> Post([FromBody]string data)

  object response = await ExecuteServerLogicsAsync(data);
  return response;

这将使您的服务器在整体上更具响应性和可扩展性(即不会被许多请求淹没)。

【讨论】:

非常感谢。我们在 task.run 中调用我们的方法的主要原因是,我们的逻辑的主要工作是从数据库中查询数据(我们使用的是没有实体框架的 ODP.net)。问题是“等待”必须应用于返回Task的方法调用,但我们的逻辑中没有返回Tasks,我们主要使用ODP命令并将我们的数据包装在公共对象中,该对象将被序列化为JSON。因此,我们无法从逻辑方法返回 Task 并在 Task.Run 中调用它。 Hםw 我们可以使用-“等待 ExecuteServerLogicsAsync(data);”来实施您的建议吗? ? 当您使用 asyncawait 时,HttpContext.Current 会适当地流动。如果您明确使用后台线程,它不会流动,例如,Task.Run 1. ASP.NET 的设计目的不是为了在服务 HTTP 请求之外做额外的工作。 2. 我在回答中概述了架构; WebAPI 是解决方案的部分。 3. WCF 不会解决您遇到的任何性能问题。 这不再是原始问题的一部分。 @StephenCleary 我知道这是一些线程死灵,但感谢您对手动执行 Task.Run 时上下文未恢复的评论...这在 WebAPI 中杀死了我:D【参考方案2】:

如果您的任务在您的 ApiController 派生类中,您可以使用:

var ctx = this.Request.Properties["MS_HttpContext"] as System.Web.HttpContextWrapper;

这将为您提供一个包含所有常用属性的 HttpContext 包装器。

【讨论】:

【参考方案3】:

Amir 我认为您正在寻找类似下面的内容。我一直在处理同样的问题,试图优化一系列调用。它需要一直是异步的,这意味着您的 ExecuteServerLogics() 需要是异步的,并且您还必须将包含的 lamda 标记为异步。

我相信遵循这种模式可能会消除大部分性能问题。很好地通过这样的方式传递上下文。

public async Task<object> Post([FromBody]string data)

      // Save worker context:
      var currentContext = HttpContext.Current; 

      object response = await Task<object>.Run(async () =>
      
           // Set the context of the current task :
           HttpContext.Current = currentContext ;

           // Executes logics for current request:
           return await ExecuteServerLogics(data);
      );

      return response;

【讨论】:

以上是关于Web API 服务 - 如何在异步任务中使用“HttpContext.Current”的主要内容,如果未能解决你的问题,请参考以下文章

如何知道以编程方式在我当前的应用程序中运行了多少异步任务/服务?

如何使用 XCTestCases 实现异步任务依赖?

Web开发之旅--Flask使用Celery执行异步任务

Swoole 的异步 Task 任务详解

为啥我们需要异步任务以任何方式默认 Web API 请求将仅通过创建或重用现有线程来运行

在 web api 控制器(.net 核心)中使用 async/await 或任务