Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Web API Service - How to use "HttpContext.Current" inside async task

I'm using a "Post" async method of webApi rest service:

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

      return response;
}

This above code worked good but in some of the client's calls, we experienced performance issues.

After reading some articles here, i've noticed that our webApi rest service, is not really working asynchronously with its incoming web requests, because we forgot to use async/await pattern :

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

      return response;
}

After this fix we noticed the performance got better, but we found another critic problem: when accessing HttpContext.Current - it returns Null reference:

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

We tried to found a solution for it, and in most posts we found that we should pass the worker thread's HttpContext reference into the inner Task that executes the server logics. The problem with this solution is that the server's logics methods, use many static classes that use "HttpContext.Current" such as -

  1. Loggers calls.
  2. static security classes that retrieves the user.identity
  3. static security classes that retrives the incoming request's session data, etc.

Therefore, passing the "HttpContext.Current" reference of the worker thread won't solve it.

When we tried the next solution:

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

for some reason, we noticed the performance got worse again, like it had returned working synchronously again.

Our problems are:

1. Why in the last example, setting the "HttpContext.Current" inside the await task, causes the requests to return the same bad performance results which similar to the synchronous results?

2. Is there another way we can use "HttpContext.Current" inside the inner task that call - "ExecuteServerLogics", and in all the static classes which also call "HttpContext.Current"? am I doing the entire design wrong somehow?

Thanks!

like image 801
AmirTNinja Avatar asked May 21 '14 17:05

AmirTNinja


2 Answers

From the beginning:

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

Don't ignore compiler warnings; the compiler will generate a warning for this method that specifically states it will run synchronously.

Moving on:

in some of the client's calls, we experienced performance issues.

Asynchronous code on the server will not be faster for a single call in isolation. It only helps you scale your server.

In particular, Task.Run will negate all the performance benefits of async and then degrade performance a bit beyond that. I believe the improvement in performance that you measured was coincidental.

in most posts we found that we should pass the worker thread's HttpContext reference into the inner Task that executes the server logics.

Those posts are wrong. IMHO. You end up using the HttpContext object from a background thread, when that object is specifically designed to be only accessed from a request thread.

am I doing the entire design wrong somehow?

I do recommend you take a step back and think about the big picture. When a request comes in, it has a certain amount of work to do. Whether that work is done synchronously or asynchronously is immaterial to the client; both approaches will take about the same amount of time.

If you need to return early to the client, then you'll need a completely different architecture. The usual approach is to queue the work to a reliable queue (e.g., Azure queue), have a separate backend (e.g., Azure WebRole), and proactively notify the client when the work is completed (e.g., SignalR).

That's not to say that async is useless, though. If ExecuteServerLogics is an I/O bound method, then it should be made asynchronous rather than blocking, and then you can use asynchronous methods as such:

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

This will enable your server to be more responsive and scalable overall (i.e., not get overwhelmed by many requests).

like image 85
Stephen Cleary Avatar answered Nov 14 '22 22:11

Stephen Cleary


If your task is inside your ApiController-derived class, you can use:

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

This will give you an HttpContext wrapper with all the usual properties.

like image 26
Ricardo Peres Avatar answered Nov 15 '22 00:11

Ricardo Peres