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

时间:2014-05-21 17:40:34

标签: asp.net asynchronous asp.net-web-api async-await httpcontext

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

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

      return response;
}

以上代码效果很好,但在某些客户的电话中,我们遇到了性能问题。

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

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”,例如 -

  1. 记录器调用。
  2. 检索user.identity的静态安全类
  3. 用于检索传入请求的会话数据等的静态安全性类。
  4. 因此,传递工作线程的“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 个答案:

答案 0 :(得分: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;
}

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

答案 1 :(得分:2)

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

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

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

答案 2 :(得分:0)

阿米尔(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;
}