Служба веб-API - Как использовать HttpContext.Current внутри асинхронной задачи

Я использую асинхронный метод "Post" сервиса отдыха webApi:

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

      return response;
}

Приведенный выше код работал хорошо, но при некоторых обращениях клиента мы испытывали проблемы с производительностью.

Прочитав здесь несколько статей, я заметил, что наша служба отдыха webApi на самом деле не работает асинхронно со своими входящими веб-запросами, потому что мы забыли использовать шаблон 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. статические классы безопасности, которые извлекают user.identity
  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. Почему в последнем примере установка "HttpContext.Current" внутри задачи ожидания приводит к тому, что запросы возвращают такие же плохие результаты производительности, которые похожи на синхронные результаты?

2. Есть ли другой способ использования «HttpContext.Current» внутри вызывающей внутренней задачи - «ExecuteServerLogics» и во всех статических классах, которые также вызывают "HttpContext.Current"? я как-то неправильно делаю весь дизайн?

Спасибо!


person AmirTNinja    schedule 21.05.2014    source источник


Ответы (3)


С самого начала:

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 - это метод, связанный с вводом-выводом, то его следует сделать асинхронным, а не блокирующим, и тогда вы можете использовать асинхронные методы как таковые:

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

Это позволит вашему серверу быть более отзывчивым и масштабируемым в целом (то есть не перегружаться множеством запросов).

person Stephen Cleary    schedule 21.05.2014
comment
Большое спасибо. Основная причина, по которой мы назвали наш метод внутри task.run, заключается в том, что основная работа нашей логики - это запрос данных из БД (мы используем ODP.net без структуры сущностей). Проблема заключалась в том, что ожидание должно применяться к вызову метода, который возвращает Task ‹object›, но у нас нет задач, возвращаемых в нашей логике, мы в основном используем команды ODP и оборачиваем наши данные в общий объект, который будет сериализован в JSON. Следовательно, мы не могли вернуть Task ‹object› из нашего метода логики и вызвали его внутри Task.Run. Как мы можем реализовать ваше предложение с помощью - await ExecuteServerLogicsAsync (data); ? - person AmirTNinja; 22.05.2014
comment
Вам придется подождать, пока ODP поддержит async или использовать EF6. - person Stephen Cleary; 22.05.2014
comment
Для меня все еще есть что-то неясное, если я правильно понял - для моего метода логики нет альтернативы для поддержки использования async \ await? например, как-то обернуть код логики в какую-то настраиваемую задачу, которая обертывает мою логику? Я могу использовать await только с классами фреймворка, которые были предназначены для возврата объекта Task для использования в вызове await, как в EF6? - person AmirTNinja; 22.05.2014
comment
Это правильно. Вы не можете сделать что-то действительно асинхронным, если оно предоставляет только блокирующий API. Если вы пишете приложение пользовательского интерфейса, вы можете сделать вид, что оно асинхронное, заключив его в Task.Run; это сжигает поток пула потоков. Task.Run по этой причине наносит ущерб приложениям ASP.NET. - person Stephen Cleary; 22.05.2014
comment
1. В этом случае, пока я каким-то образом не использую ODP async или не использую EF6, у меня нет альтернативы, кроме как удалить использование Task.Run и использовать метод WebApi Post без шаблона async / await? 2. Если я не могу использовать async в некоторых случаях здесь, что бы вы порекомендовали сделать, чтобы повысить производительность для каждого вызова веб-запроса в логике остальных служб? - person AmirTNinja; 22.05.2014
comment
1. Правильно. 2. Нет простого ответа. Вы можете попробовать такие вещи, как параллельная обработка, но поскольку ваши базовые методы блокируются, это убьет вашу масштабируемость в обмен на более быструю обработку каждого запроса. - person Stephen Cleary; 22.05.2014
comment
Я попытаюсь. О проблеме HttpContext.Current - я все еще не могу понять какой-то момент - даже если я буду использовать, например, Async в EF6, как я смогу использовать свои статические классы, которые используют HttpContext.Current (например, регистратор) при регистрации вызовов и ошибок из операций с БД, которые будут выполняться асинхронно? Поскольку Await должен применяться к методу, возвращающему задачу, не означает ли это, что я все равно потеряю «HttpContext.Current» внутри возвращаемой области задачи? - person AmirTNinja; 22.05.2014
comment
HttpContext.Current течет правильно, когда вы используете async и await. Он не выполняется, если вы явно используете фоновый поток, например Task.Run. - person Stephen Cleary; 22.05.2014
comment
Понятно! Еще раз спасибо :) Кстати, в своем ответе вы упомянули, что если вам нужно вернуться к клиенту раньше, тогда вам понадобится совершенно другая архитектура - 1. Почему это требование не может быть выполнено в сервисе WebApi? Разве в нашем контроллере нет ничего, что можно было бы настроить или реализовать, что могло бы справиться с этой целью и улучшить производительность обработки веб-запросов? 2. Если мы не можем реализовать эту потребность в WebApi, какую архитектуру вы бы порекомендовали нам использовать вместо WebApi? 3. Является ли WCF предпочтительным с точки зрения производительности? - person AmirTNinja; 22.05.2014
comment
1. ASP.NET не предназначен для выполнения дополнительной работы, помимо обслуживания HTTP-запросов. 2. В своем ответе я обрисовываю архитектуру; WebAPI - это часть решения. 3. WCF не решит никаких проблем с производительностью, которые у вас есть. - person Stephen Cleary; 22.05.2014
comment
Что в моем случае считается дополнительной работой? Потому что наша логика - это на самом деле основные операции с БД и XML, предназначенные для обслуживания HTTP-запросов, а не более того. - person AmirTNinja; 23.05.2014
comment
Под дополнительной работой, помимо обслуживания HTTP-запросов, я подразумеваю выполнение чего-либо после отправки ответа. - person Stephen Cleary; 23.05.2014
comment
Где это связано в моем примере? после выполнения логики сервера ответ возвращается клиенту, и мне не нужно выполнять дополнительную работу, я что-то пропустил? - person AmirTNinja; 23.05.2014
comment
Это если вы выберете раннюю архитектуру возврата. - person Stephen Cleary; 23.05.2014
comment
Это очень помогает, спасибо. Об асинхронной обработке - обрабатываются ли веб-запросы на сервере WebApi асинхронно? без учета шаблона async / await, а только архитектуры WebApi - можем ли мы быть уверены, что все запросы обрабатываются асинхронно и параллельно, где каждый веб-запрос выполняется в отдельном рабочем потоке на сервере WebApi? - person AmirTNinja; 24.05.2014
comment
Это больше не является частью исходного вопроса. - person Stephen Cleary; 24.05.2014
comment
@StephenCleary Я знаю, что это некромантия потока, но спасибо за комментарий о том, что контекст не восстанавливается, когда вы вручную выполняете Task.Run ... это убивало меня в WebAPI: D - person Jonas; 10.05.2017
comment
действительно подробный ответ :) - person Nico; 31.10.2017

Если ваша задача находится внутри класса, производного от ApiController, вы можете использовать:

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

Это даст вам оболочку HttpContext со всеми обычными свойствами.

person Ricardo Peres    schedule 17.06.2015

Амир, я думаю, вы ищете что-то вроде этого ниже. Я столкнулся с той же проблемой, пытаясь оптимизировать серию вызовов. Он должен быть полностью асинхронным, что означает, что ваш 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;
}
person Underground    schedule 12.08.2018