Использование ThreadPool.QueueUserWorkItem в ASP.NET в сценарии с высоким трафиком

У меня всегда было впечатление, что использование ThreadPool для (пусть и некритических) недолговечных фоновых задач считалось лучшей практикой даже в ASP.NET, но затем я наткнулся на в этой статье, которая, как представляется, предполагает иное: аргумент заключается в том, что вы должны оставить ThreadPool для обработки связанных с ASP.NET запросов.

Итак, вот как я делал небольшие асинхронные задачи:

ThreadPool.QueueUserWorkItem(s => PostLog(logEvent))

И статья предлагает вместо этого создать поток явно, похожий на:

new Thread(() => PostLog(logEvent)){ IsBackground = true }.Start()

Первый метод имеет то преимущество, что он управляется и ограничен, но есть потенциал (если статья верна), что фоновые задачи затем соперничают за потоки с обработчиками запросов ASP.NET. Второй метод освобождает ThreadPool, но за счет неограниченного доступа и, следовательно, потенциального использования слишком большого количества ресурсов.

Итак, мой вопрос в том, правильно ли указан совет в статье?

Если ваш сайт получал так много трафика, что ваш ThreadPool был заполнен, то лучше ли выходить из диапазона, или полный поток ThreadPool подразумевает, что вы все равно получаете доступ к своим ресурсам, в каком случае вы не должны пытаться запускать свои собственные потоки?

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

Ответ 1

Другие ответы здесь, похоже, не учитывают наиболее важный момент:

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

Это касается как бесплатных потоков, созданных new Thread(...), так и рабочих потоков в ThreadPool, которые отвечают на запросы QueueUserWorkItem.

Да, это правда, вы можете голодать ThreadPool в процессе ASP.NET, ставя в очередь слишком много рабочих элементов. Это предотвратит обработку ASP.NET дополнительных запросов. Информация в этой статье является точной в этом отношении; один и тот же пул потоков, используемый для QueueUserWorkItem, также используется для обслуживания запросов.

Но если вы на самом деле слишком много работы, чтобы вызвать голод, то вы должны голодать пул потоков! Если в то же время вы выполняете буквально сотни операций с интенсивным использованием ЦП, что бы хорошо сделать, чтобы другой рабочий поток обслуживал запрос ASP.NET, когда машина уже перегружена? Если вы столкнулись с такой ситуацией, вам нужно полностью перепроектировать!

В большинстве случаев, когда я вижу или слышу о некорректном использовании многопоточного кода в ASP.NET, он не предназначен для работы в ЦП с большими нагрузками. Он предназначен для работы в режиме ожидания ввода-вывода. И , если вы хотите работать с I/O, тогда вы должны использовать поток ввода-вывода (порт завершения ввода-вывода).

В частности, вы должны использовать асинхронные обратные вызовы, поддерживаемые любым классом библиотеки, который вы используете. Эти методы всегда очень четко обозначены; они начинаются со слов Begin и End. Как и в Stream.BeginRead, Socket.BeginConnect, WebRequest.BeginGetResponse и т.д.

Эти методы используют ThreadPool, но они используют IOCP, которые не мешают запросам ASP.NET. Они представляют собой особый вид легкой нити, которая может быть "проснута" сигналом прерывания от системы ввода-вывода. А в приложении ASP.NET у вас обычно есть один поток ввода-вывода для каждого рабочего потока, поэтому каждый отдельный запрос может иметь одну асинхронную операцию в очереди. Это буквально сотни асинхронных операций без какого-либо существенного снижения производительности (при условии, что подсистема ввода-вывода может идти в ногу). Это намного больше, чем вам когда-либо понадобится.

Просто имейте в виду, что асинхронные делегаты не работают таким образом - они будут в конечном итоге использовать рабочий поток, как и ThreadPool.QueueUserWorkItem. Это только встроенные методы async для классов библиотеки .NET Framework, которые способны это сделать. Вы можете сделать это сами, но это сложно и немного опасно и, вероятно, выходит за рамки этого обсуждения.

Лучший ответ на этот вопрос, на мой взгляд, не использует ThreadPool или фоновый Thread экземпляр в ASP.NET. Это совсем не похоже на то, чтобы развернуть поток в приложении Windows Forms, где вы делаете это, чтобы поддерживать пользовательский интерфейс и не заботитесь о том, насколько он эффективен. В ASP.NET ваша проблема связана с пропускной способностью, и весь этот контекст, включающий все эти рабочие потоки, полностью уничтожит вашу пропускную способность независимо от того, используете ли вы ThreadPool или нет.

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

Ответ 2

Per Thomas Marquadt из команды ASP.NET в Microsoft, безопасно использовать ASP.NET ThreadPool (QueueUserWorkItem).

Из статьи:

Q) Если мое приложение ASP.NET использует потоки потоков CLR ThreadPool, я не буду голодать ASP.NET, который также использует CLR ThreadPool для выполнения запросов? Blockquote

A) [T] o подведите итог, не беспокойтесь о голодающий ASP.NET потоков, и если вы думаете, что здесь проблема я знаю и хорошо позабочусь об этом.

Q) Должен ли я создавать свои собственные потоки (новый поток)? Не будет лучше для ASP.NET, поскольку он использует CLR ThreadPool.

A) Пожалуйста, не делайте. Или поставить по-другому, нет!!! Если вы действительно умнее, умнее меня, тогда ты может создавать собственные потоки; в противном случае, даже не думайте об этом. Вот несколько причин, почему вы должны не часто создавать новые потоки:

1) Это очень дорого, по сравнению с QueueUserWorkItem... Кстати, если вы можете написать лучший ThreadPool, чем CLR, я рекомендую вам подать заявку на работу в Microsoft, потому что определенно искали таких людей, как вы!.

Ответ 3

Веб-сайты не должны идти вокруг нереста потоков.

Обычно вы перемещаете эту функциональность в службу Windows, с которой вы тогда общаетесь (я использую MSMQ для общения с ними). ​​

- Изменить

Я описал реализацию здесь: Обработка фона на основе очереди в веб-приложении ASP.NET MVC

- Изменить

Чтобы расширить, почему это даже лучше, чем просто потоки:

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

Он также позволяет выполнять пакетную обработку любой задачи, которую вы пытались выполнить (отправить электронную почту/все).

Ответ 4

Я определенно считаю, что общая практика быстрой и низкоприоритетной асинхронной работы в ASP.NET будет заключаться в использовании пула потоков .NET, особенно для сценариев с высоким трафиком, так как вы хотите ограничить свои ресурсы.

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

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

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

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

Ответ 5

Вы должны использовать QueueUserWorkItem и избегать создания новых потоков, как если бы вы избежали чумы. Для визуального объяснения, почему вы не будете голодать ASP.NET, поскольку он использует тот же ThreadPool, представьте себе очень опытного жонглера, используя две руки, чтобы держать полдюжины боулингов, мечей или что-то в полете. Для того, чтобы понять, почему создание собственных потоков плохо, представьте, что происходит в Сиэтле в час пик, когда сильно используемые входные пандусы на шоссе позволяют автомобилям сразу вводить трафик, вместо того, чтобы использовать свет и ограничивать количество входов на каждые каждые несколько секунд, Наконец, для подробного объяснения см. Эту ссылку:

http://blogs.msdn.com/tmarq/archive/2010/04/14/performing-asynchronous-work-or-tasks-in-asp-net-applications.aspx

Спасибо, Томас

Ответ 6

Эта статья неверна. ASP.NET имеет собственный пул потоков, управляемых потоков рабочих потоков, для обслуживания запросов ASP.NET. Этот пул обычно состоит из нескольких сотен потоков и отдельно от пула ThreadPool, который представляет собой несколько меньших кратных процессоров.

Использование ThreadPool в ASP.NET не будет мешать рабочим потокам ASP.NET. Использование ThreadPool в порядке.

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

Использование нового потока для каждого сообщения определенно переполняется.

Другая альтернатива, если вы говорите только о регистрации, - это использовать библиотеку, такую ​​как log4net. Он обрабатывает ведение журнала в отдельном потоке и заботится обо всех проблемах контекста, которые могут возникнуть в этом сценарии.

Ответ 7

Я бы сказал, что статья неверна. Если вы используете большой .NET-магазин, вы можете безопасно использовать пул через несколько приложений и несколько веб-сайтов (используя отдельные пулы приложений), просто на основе одного оператора в ThreadPool:

Существует один пул потоков для каждого процесса.Пул потоков имеет размер по умолчанию 250 рабочих потоков за каждый доступный процессор и 1000 операций ввода/вывода потоки. Количество потоков в пул потоков можно изменить, используя метод SetMaxThreads. Каждая нить использует размер стека по умолчанию и работает с приоритетом по умолчанию.

Ответ 8

На прошлой неделе мне задали аналогичный вопрос, и я дам вам тот же ответ. Почему вы используете многопоточные веб-приложения для каждого запроса? Веб-сервер представляет собой фантастическую систему, которая в значительной степени оптимизирована для своевременного предоставления множества запросов (т.е. Многопоточности). Подумайте, что происходит, когда вы запрашиваете почти любую страницу в Интернете.

  • Запрос для некоторой страницы
  • Html отсылается
  • Html сообщает клиенту, чтобы он делал дополнительные запросы (js, css, images и т.д.).
  • Дополнительная информация возвращается

Вы приводите пример удаленного ведения журнала, но это должно быть проблемой вашего регистратора. Асинхронный процесс должен иметь место для своевременного получения сообщений. Сэм даже указывает, что ваш регистратор (log4net) должен уже поддерживать это.

Сэм также прав в том, что использование пула потоков в среде CLR не вызовет проблем с пулом потоков в IIS. Тем не менее, дело в том, что вы не создаете нити из процесса, вы создаете новые потоки из потоков потоков IIS. Есть разница, и различие имеет важное значение.

Threads vs Process

Оба потока и процессы - это методы распараллеливания приложения. Однако процессы независимы исполнительные подразделения, которые содержат свои собственные информации штата, использовать свои собственные адресные пространства и взаимодействовать только с друг друга через межпроцессный механизмы коммуникации (обычно управляемый операционной системой). Обычно приложения разделяются в процессы во время проектирования фаза и мастер-процесс явно порождает подпроцессы, когда он делает смысл логически разделить значительная функциональность приложения. Процессы, другими словами, являются архитектурная конструкция.

Напротив, поток - это кодирование конструкции, которая не влияет на архитектуры приложения. один процесс может содержать несколько потоки; все потоки в процессе совместно использовать одно и то же состояние и одну и ту же память пространства и может связываться с каждым другие, поскольку они разделяют те же переменные.

Источник

Ответ 9

Я не согласен с указанной статьей (С# feeds.com). Легко создать новый поток, но опасно. Оптимальное количество активных потоков для работы на одном ядре на самом деле удивительно мало - меньше 10. Слишком легко заставить машину тратить время на переключение потоков, если потоки создаются для небольших задач. Темы - это ресурс, который требует обслуживания REQUIRE. Для этого требуется абстракция WorkItem.

Здесь существует компромисс между сокращением количества потоков, доступных для запросов, и созданием слишком большого количества потоков, чтобы позволить любому из них эффективно обрабатывать. Это очень динамичная ситуация, но я думаю, что нужно активно управлять (в данном случае пулом потоков), а не оставлять ее процессору, чтобы оставаться впереди создания потоков.

Наконец, в статье приводятся довольно подробные заявления об опасностях использования ThreadPool, но для их резервного копирования действительно нужно что-то конкретное.

Ответ 10

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

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