Что происходит в BeginProcessRequest()?

Мы используем NewRelic для обеспечения трассировки приложений на стороне сервера.

Мы заметили, что некоторые из наших приложений последовательно тратят около 100 мс в методе System.Web.Mvc.MvcHandler.BeginProcessRequest().

Это происходит до того, как вызывается какой-либо пользовательский код контроллера (который регистрируется отдельно, а не кумулятивно) - неясно, почему в этом методе было бы так много времени.

Какие вещи MVC сделает в этом методе? Может ли это просто быть в очереди запросов?

[РЕДАКТИРОВАТЬ:] Как и предполагалось - ответ Скаляра ниже был на месте. Мы удалили и оптимизировали все наши зависимости сеанса и увидели значительное увеличение масштабируемости и стабильности приложений

Ответ 1

То, что вы можете видеть, обычно называют гибкостью потока в .NET.

То, что вы, вероятно, видите, что результаты под ярлыком (например, код приложения в System.Web.HttpApplication.BeginRequest()) - проблема гибкости потока; в большинстве случаев время, которое вы видите здесь, не обязательно выполняется кодом, а веб-контекст, ожидающий, когда потоки будут выпущены обратно из него из блокировки чтения-записи.

Application_BeginRequest() "пауза" - это та, которая довольно распространена в веб-стеке ASP.NET. В общем, когда вы видите длительное время загрузки в BeginRequest, вы имеете дело с гибкостью потока ASP.NET и/или блокировками потоков - особенно при работе с операциями ввода-вывода и сеанса. Не то чтобы это плохо, это как раз .net гарантирует, что ваши потоки остаются параллельными.

Временной промежуток обычно возникает между BeginRequest и PreRequestHandlerExecute. Если приложение записывает несколько вещей в сеанс, тогда ASP.NET выдает блокировку чтения-записи на HttpContext.Current.Session.

Хорошим способом убедиться в том, что это проблема, с которой вы столкнулись, было бы проверить идентификаторы потоков, чтобы узнать, является ли маневренность проблемой - идентификаторы будут отличаться для данного запроса.

Например. Во время отладки, возможно, вы можете добавить следующее к своему Global.asax.cs:

protected void Application_BeginRequest(Object sender, EventArgs e) { 
      Debug.WriteLine("BeginRequest_" + Thread.CurrentThread.ManagedThreadId.ToString()); 
   }

Откройте окно вывода отладки (из Visual Studio: View → Output, затем выберите "Debug" из раскрывающегося списка "show output from" ).

Во время отладки нажмите страницу, где вы видели долгое время. Затем просмотрите выходной журнал - если вы видите несколько идентификаторов, то вы можете страдать от этого.

Вот почему вы можете иногда задерживать задержку, но не в другое время, код приложения может использовать сеанс несколько иначе или сеанс или операции ввода-вывода могут быть выше или ниже со страницы на страницу.

Если это так, некоторые вещи можно сделать, чтобы ускорить процесс в зависимости от того, как сеанс используется на сайте или на каждой заданной странице.

Для страниц, которые не изменяют сеанс:

   <% @Page EnableSessionState="ReadOnly" %>

Для страниц, которые не используют состояние сеанса:

<% @Page EnableSessionState="False" %>

Если приложение не использует сеанс (web.config):

<configuration>
    <system.web>
      <sessionState mode="Off" />
    </system.web>
</configuration>

Итак, давайте рассмотрим следующий пример:

Пользователь загружает страницу, а затем решает перейти на другую страницу до того, как будет выполнен первый запрос. Загрузка ASP.NET заставит блокировку сеанса вызвать загрузку запроса новой страницы до тех пор, пока не завершится запрос первой страницы. С ASP.NET MVC каждое действие блокирует сеанс пользователя для синхронизации; вызывая ту же проблему.

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

Кстати, элемент управления UpdatePanel вызывает одно и то же поведение -

http://msdn.microsoft.com/en-us/magazine/cc163413.aspx

Что можно сделать:

Эта проблема блокировки является одной из причин, по которым у Microsoft есть класс SessionStateUtility -

http://msdn.microsoft.com/en-us/library/system.web.sessionstate.sessionstateutility.aspx

Чтобы вы могли переопределить поведение по умолчанию, если вы столкнулись с этой проблемой, как показано здесь в этом Реализация Redis: https://github.com/angieslist/AL-Redis

Существует множество опций поставщика состояния по умолчанию, используемого веб-сайтами на основе .net. Но, как известно, это время транзакции указывает на то, что потоки блокируются и ждут по завершении запросов к серверу.

Ответ 2

Если вы хотите, чтобы определенный контроллер обрабатывал запросы параллельно от одного пользователя, вы можете использовать атрибут с именем SessionState, который был введен в MVC с версии 3. Не нужно использовать контроллер без сеанса для того, чтобы достигните parallelism, опция Ready-only для SessionStateBehavior позволит вам пройти проверки безопасности, которые вы, возможно, реализовали на основе данных сеанса.

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public class ParallelController : Controller
{
    ...
}

У меня также были задержки в System.Web.Mvc.MvcHandler.BeginProcessRequest(), когда вы пытались выполнить несколько длительных действий, и я увидел его в NewRelic. Эти атрибуты решили проблему и дали возможность обрабатывать параллельные действия для этого контроллера.

Ответ 3

Я столкнулся с этой же проблемой в приложении, которое очень минимально использовало сеансы. Принимая во внимание принятый ответ и даже временно удаляя SessionState полностью для диагностических целей, у меня все еще были значительные проблемы с производительностью в этом "Методе"

Основываясь на комментарии todd_saylor в этот поток, я провел много исследований на своем собственном сайте и обнаружил, что BeginProcessRequest может служить Новый Relic - все для разных потерь производительности, которые появляются в разных областях кода. Я смог значительно сократить время в этой области, используя код профилирования на моей локальной машине с использованием профилировщика профилей Red Gate ANTs.

В моем локальном профилировании было несколько вещей:

  • Я использовал Ninject в качестве моего контейнера DI, что вызвало потерю производительности в его разрешении Object. Я заменил его на SimpleInjector и увеличил производительность на порядок.
  • Я обнаружил, что где-то между AutoMapper IQueryable Extensions Project().To<>() и поставщиком Linq Sql Server, что динамическое компиляция и JITing прогнозов отвечали за 50% моего времени запроса. Замена этого с помощью CompiledQuery сделала еще одну значительную проблему.

Надеюсь, это поможет кому-то еще!

Ответ 4

всем, кто читает этот опыт использования IIS с высоким уровнем использования процессора, который необъяснен, взгляните на этот поток из форума поддержки NewRelic, который я открыл.

Я занимаюсь этим вопросом более недели, пока не понял, что причиной этого является его агент.

. NET-агент, вызывающий высокий уровень использования ЦП в IIS

Итак, прежде всего я предлагаю вам попробовать удалить агент .NET и посмотреть, нет ли каких-либо изменений, прежде чем вы погрузитесь в более сложные эксперименты.

Я отправляю этот ответ по этому конкретному вопросу, так как я пришел сюда первым, работая над проблемой, и гибкость потоков поставила меня на длинный путь, который был неправильным... (хотя очень интересно сам).