Подтвердить что ты не робот

Is Task.Run считается плохой практикой в ​​веб-приложении ASP.NET MVC?

Фон

В настоящее время мы разрабатываем веб-приложение, основанное на ASP.NET MVC 5, Angular.JS 1.4, веб-API 2 и Entity Framework 6. По причинам масштабируемости маневренность веб-приложений зависит от шаблона async/await, Для нашего домена требуются некоторые интенсивные вычисления, которые могут занимать несколько секунд (< 10s). Раньше некоторые члены команды использовали Task.Run, чтобы ускорить вычисления. Поскольку запуск дополнительного потока внутри ASP.NET MVC или контроллеров Web API считается плохой практикой (нить не известна IIS, поэтому рассмотренные в AppDomain Recycle = > См. сообщение в блоге Стивена Клири), они использовали ConfigureAwait (false).

Пример

public async Task CalculateAsync(double param1, double param2)
{
    // CalculateSync is synchronous and cpu-intensive (<10s)
    await Task.Run(() => this.CalculateSync(param1, param2))).ConfigureAwait(false);
}

Вопросы

  • Есть ли какое-либо преимущество в производительности при использовании Task.Run в async Web API Controller для операций с привязкой к процессору?
  • Неужели ConfigureAwait (false) действительно избегает создания дополнительного потока?
4b9b3361

Ответ 1

Есть ли какое-либо преимущество в производительности при использовании Task.Run в асинхронном веб-API-контроллере для операций cpu-bound?

Ноль. Никто. Фактически, вы препятствуете выполнению, создавая новый поток. В контексте веб-приложения нереста потока - это не то же самое, что работает в "фоновом режиме". Это связано с характером веб-запроса. Когда есть входящий запрос, поток берется из пула для обслуживания запроса. Использование async позволяет возвращать поток до конца запроса тогда и только тогда, когда поток находится в состоянии ожидания, то есть в режиме ожидания. Создавая поток для работы, эффективно простаивает основной поток, позволяя ему возвращаться в пул, но у вас все еще есть активный поток. Возврат исходной нити в пул ничего не делает в этот момент. Затем, когда новый поток завершит свою работу, вы должны запросить основной поток из пула и, наконец, вернуть ответ. Ответ не может быть возвращен до завершения всей работы, поэтому, если вы используете 1 нить или сто, асинхронную или синхронизацию, ответ не может быть возвращен, пока все не закончится. Поэтому использование дополнительных потоков ничего не делает, кроме добавления служебных данных.

Неужели ConfigureAwait (false) действительно избегает создания дополнительного потока?

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

Ответ 2

Есть ли какое-либо преимущество в производительности при использовании Task.Run в async Web API Controller для операций с привязкой к процессору?

Нет. И не имеет значения, связан ли он с CPU или нет.

Task.Run Работает в потоке ThreadPool. В запросе web api уже используется поток ThreadPool, поэтому вы просто ограничиваете масштабируемость путем разгрузки на другой поток без причины.

Это полезно в приложениях пользовательского интерфейса, где поток пользовательского интерфейса представляет собой отдельный отдельный поток.

Неужели ConfigureAwait (false) действительно избегает создания дополнительного потока?

Это не влияет на создание потоков так или иначе. Все, что он делает, настраивает, будет ли возобновлено захваченное SynchronizationContext или нет.

Ответ 3

Есть ли какое-либо преимущество в производительности при использовании Task.Run в асинхронном веб-API-контроллере для операций cpu-bound?

Подумайте о том, что на самом деле происходит - Task.Run() создает задачу в пуле потоков, и ваш оператор await освободит поток (я также предполагаю, что все методы в стеке также ждут). Теперь ваш поток возвращается в пул, и он может забрать ту же задачу! В этом случае, очевидно, нет увеличения производительности. На самом деле есть удар производительности. Но если поток подхватит еще одну задачу (возможно, что произойдет), другой поток должен будет забрать CalculateSync() Задачу и продолжить с того места, где бывшая остановилась. Было бы больше сцены, чтобы исходный поток выполнял CalculateSync() на первом месте, никаких задач не было, а другой поток имел другие задачи в очереди.

Неужели ConfigureAwait (false) действительно избегает создания дополнительного потока?

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

Ответ 4

Есть еще одна вещь, которую вам нужно рассмотреть. Когда вы сказали, что ваш api делает задачу с интенсивным процессором, асинхронно/ждут помощи, чтобы запустить процесс в другом потоке и освободить пул приложений для другого запроса. Значит, ваш веб-api может обрабатывать большее количество запросов в секунду, если вы правильно используете async/await.

В вашем случае выглядят как this.CalculateSync(param1, param2) неасинхронный метод, поэтому для асинхронного вызова вы должны использовать Task.Run.

Я рекомендую удалить .ConfigureAwait(false), так как это фактически снизит вашу производительность.