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

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

10 ответов

Решение

Другие ответы здесь, кажется, опускают самый важный момент:

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

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

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

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

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

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

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

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

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

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

По словам Томаса Марквадта из команды ASP.NET в Microsoft, безопасно использовать ASP.NET ThreadPool (QueueUserWorkItem).

Из статьи:

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

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

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

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

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

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

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

-- Редактировать

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

-- Редактировать

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

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

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

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

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

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

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

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

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

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

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

Вы можете использовать Parallel.For или Parallel.ForEach и определить предел возможных потоков, которые вы хотите выделить для бесперебойной работы и предотвращения истощения пула.

Однако при работе в фоновом режиме вам нужно будет использовать чистый стиль TPL ниже в веб-приложении ASP.Net.

var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;

ParallelOptions po = new ParallelOptions();
            po.CancellationToken = ts.Token;
            po.MaxDegreeOfParallelism = 6; //limit here

 Task.Factory.StartNew(()=>
                {                        
                  Parallel.ForEach(collectionList, po, (collectionItem) =>
                  {
                     //Code Here PostLog(logEvent);
                  }
                });

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

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

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

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

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

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

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

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

  1. Запрос сделан для некоторой страницы
  2. HTML возвращается
  3. Html говорит клиенту сделать дальнейшие запросы (js, css, images и т. Д.)
  4. Дополнительная информация возвращается

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

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

Потоки против процесса

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

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

Источник

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

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

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

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

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

Другие вопросы по тегам