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

9

我正在使用WebApi Rest服务的"Post"异步方法:

public async Task<object> Post([FromBody]string data)
{
      object response = ExecuteServerLogics(data);

      return response;
}

以上代码效果良好,但在一些客户端调用中,我们遇到了性能问题。

阅读了一些文章后,我注意到我们的WebApi Rest服务并没有真正地与其传入的Web请求异步工作, 因为我们忘记使用async/await模式

public async Task<object> Post([FromBody]string data)
{
      object response = await Task<object>.Run( () =>
      {
           return ExecuteServerLogics(data);
      });

      return response;
}

在此修复之后,我们注意到性能有所改善,但我们发现另一个严重的问题: 当访问HttpContext.Current时,它返回空引用:

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",例如:

  1. 日志记录器调用。
  2. 检索用户身份的静态安全类。
  3. 检索传入请求的会话数据等静态安全类。

因此,传递工作线程的"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. 是否有另一种方法可以在调用 "ExecuteServerLogics" 的内部任务以及所有调用 "HttpContext.Current" 的静态类中使用 "HttpContext.Current"?我是否在整个设计上都做错了一些事情?
谢谢!
3个回答

12

从一开始:

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中调用我们的方法的主要原因是,我们的逻辑的主要工作是从DB查询数据(我们使用ODP.net而不是实体框架)。问题在于,“await”必须应用于返回Task<object>的方法调用,但是我们的逻辑中没有返回Tasks,我们主要使用ODP命令,并将我们的数据包装在普通对象中,该对象将被序列化为JSON。因此,我们无法从我们的逻辑方法中返回Task<object>,并在其中调用它Task.Run。我们应该如何使用“await ExecuteServerLogicsAsync(data);”实现您的建议? - AmirTNinja
2
当您使用asyncawait时,HttpContext.Current会适当地流动。如果您明确使用后台线程(例如Task.Run),则不会流动。 - Stephen Cleary
1
  1. ASP.NET并不是为了完成除了处理HTTP请求以外的额外工作而设计的。
  2. 我在我的回答中概述了架构;WebAPI只是解决方案的一部分。
  3. WCF无法解决您所遇到的任何性能问题。
- Stephen Cleary
2
这已经不是原始问题的一部分了。 - Stephen Cleary
1
@StephenCleary 我知道这是一些线程的死灵法,但感谢您关于手动执行Task.Run时上下文不会恢复的评论...这在WebAPI中让我很烦恼 :D - Jonas
显示剩余13条评论

2

如果你的任务在你的ApiController派生类中,你可以使用以下代码:

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

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

0

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

我相信遵循这个模式,您可能可以消除大部分性能问题。很好地通过传递上下文。

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

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